From ae9b619656b900ab92b5fff2dd680928063a322d Mon Sep 17 00:00:00 2001 From: Angel Mendez Cano Date: Sat, 6 Jul 2024 19:31:17 -0600 Subject: [PATCH 1/3] feat: add enquirer dependency - add enquirer in order to handle prompts --- package-lock.json | 56 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 57 insertions(+) diff --git a/package-lock.json b/package-lock.json index 2d1c2b8bedd..0ef48da4fd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "decache": "4.6.2", "dot-prop": "9.0.0", "dotenv": "16.4.5", + "enquirer": "^2.4.1", "env-paths": "3.0.0", "envinfo": "7.13.0", "etag": "1.8.1", @@ -7724,6 +7725,14 @@ "string-width": "^4.1.0" } }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", @@ -10936,6 +10945,29 @@ "node": ">=10.13.0" } }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/enquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -28834,6 +28866,11 @@ "string-width": "^4.1.0" } }, + "ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==" + }, "ansi-escapes": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", @@ -31081,6 +31118,25 @@ "tapable": "^2.2.0" } }, + "enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "requires": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", diff --git a/package.json b/package.json index 292375a41eb..83d75f51aae 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "decache": "4.6.2", "dot-prop": "9.0.0", "dotenv": "16.4.5", + "enquirer": "^2.4.1", "env-paths": "3.0.0", "envinfo": "7.13.0", "etag": "1.8.1", From a4cd8e2c9df83f7a5c8d442e6d216c8e2afa79f7 Mon Sep 17 00:00:00 2001 From: Angel Mendez Cano Date: Mon, 8 Jul 2024 09:33:04 -0600 Subject: [PATCH 2/3] feat: migrate to enquirer --- src/commands/addons/addons-config.ts | 33 ++++---- src/commands/addons/addons-create.ts | 5 +- src/commands/addons/addons-delete.ts | 6 +- src/commands/base-command.ts | 23 +++--- src/commands/deploy/deploy.ts | 31 +++---- src/commands/env/env-list.ts | 8 +- src/commands/functions/functions-create.ts | 81 ++++++++++--------- src/commands/functions/functions-invoke.ts | 7 +- src/commands/init/init.ts | 10 +-- src/commands/integration/deploy.ts | 14 ++-- src/commands/link/link.ts | 26 +++--- src/commands/logs/build.ts | 6 +- src/commands/logs/functions.ts | 6 +- src/commands/main.ts | 7 +- src/commands/recipes/recipes.ts | 6 +- src/commands/sites/sites-create-template.ts | 14 ++-- src/commands/sites/sites-create.ts | 9 ++- src/commands/sites/sites-delete.ts | 10 +-- src/commands/switch/switch.ts | 8 +- src/lib/edge-functions/editor-helper.ts | 6 +- src/recipes/blobs-migrate/index.ts | 10 +-- src/recipes/vscode/index.ts | 10 +-- src/utils/addons/prompts.ts | 8 +- src/utils/build-info.ts | 28 ++++--- src/utils/gh-auth.ts | 12 +-- src/utils/init/config-manual.ts | 15 ++-- src/utils/init/utils.ts | 30 +++---- .../commands/logs/functions.test.ts | 14 +++- .../__snapshots__/recipes.test.js.snap | 5 -- .../integration/commands/sites/sites.test.ts | 4 +- 30 files changed, 237 insertions(+), 215 deletions(-) diff --git a/src/commands/addons/addons-config.ts b/src/commands/addons/addons-config.ts index 9d23fe36380..00d197c2668 100644 --- a/src/commands/addons/addons-config.ts +++ b/src/commands/addons/addons-config.ts @@ -1,5 +1,5 @@ import { OptionValues } from 'commander' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import isEmpty from 'lodash/isEmpty.js' import compare from '../../utils/addons/compare.js' @@ -88,14 +88,14 @@ export const addonsConfig = async (addonName: string, options: OptionValues, com return false } - const updatePrompt = await inquirer.prompt([ + const updatePrompt = await Enquirer.prompt([ { - type: 'confirm', - name: 'updateNow', - message: `Do you want to update config values?`, - default: false, - }, - ]) + type: 'confirm', + name: 'updateNow', + message: `Do you want to update config values?`, + initial: false, + }, + ]) if (!updatePrompt.updateNow) { log('Sounds good! Exiting configuration...') return false @@ -111,7 +111,8 @@ export const addonsConfig = async (addonName: string, options: OptionValues, com config: manifest.config, configValues: currentConfig, }) - const userInput = await inquirer.prompt(prompts) + // TODO: Fix type argument + const userInput = await Enquirer.prompt(prompts as any) // Merge user input with the flags specified const newConfig = updateConfigValues(manifest.config, currentConfig, userInput) @@ -134,14 +135,14 @@ export const addonsConfig = async (addonName: string, options: OptionValues, com }) log() - const confirmPrompt = await inquirer.prompt([ + const confirmPrompt = await Enquirer.prompt([ { - type: 'confirm', - name: 'confirmChange', - message: `Do you want to publish the updated "${addonName} add-on" settings for ${chalk.cyan(siteData.name)}?`, - default: false, - }, - ]) + type: 'confirm', + name: 'confirmChange', + message: `Do you want to publish the updated "${addonName} add-on" settings for ${chalk.cyan(siteData.name)}?`, + initial: false, + }, + ]) if (!confirmPrompt.confirmChange) { log('Canceling changes... You are good to go!') diff --git a/src/commands/addons/addons-create.ts b/src/commands/addons/addons-create.ts index 1fa244725d3..b58e1236a83 100644 --- a/src/commands/addons/addons-create.ts +++ b/src/commands/addons/addons-create.ts @@ -1,5 +1,5 @@ import { OptionValues } from 'commander' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import isEmpty from 'lodash/isEmpty.js' import { ADDON_VALIDATION, prepareAddonCommand } from '../../utils/addons/prepare.js' @@ -89,7 +89,8 @@ export const addonsCreate = async (addonName: string, options: OptionValues, com configValues: rawFlags, }) - const userInput = await inquirer.prompt(prompts) + // TODO: fix argument + const userInput = await Enquirer.prompt(prompts as any) // Merge user input with the flags specified configValues = updateConfigValues(manifest.config, rawFlags, userInput) const missingRequiredValues = missingConfigValues(required, configValues) diff --git a/src/commands/addons/addons-delete.ts b/src/commands/addons/addons-delete.ts index 32f9e46eb61..4a7f73e2f59 100644 --- a/src/commands/addons/addons-delete.ts +++ b/src/commands/addons/addons-delete.ts @@ -1,5 +1,5 @@ import { OptionValues } from 'commander' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import { ADDON_VALIDATION, prepareAddonCommand } from '../../utils/addons/prepare.js' import { error, exit, log } from '../../utils/command-helpers.js' @@ -12,11 +12,11 @@ export const addonsDelete = async (addonName: string, options: OptionValues, com validation: ADDON_VALIDATION.EXISTS, }) if (!options.force && !options.f) { - const { wantsToDelete } = await inquirer.prompt({ + const { wantsToDelete } = await Enquirer.prompt({ type: 'confirm', name: 'wantsToDelete', message: `Are you sure you want to delete the ${addonName} add-on? (to skip this prompt, pass a --force flag)`, - default: false, + initial: false, }) if (!wantsToDelete) { exit() diff --git a/src/commands/base-command.ts b/src/commands/base-command.ts index 592f464b755..7b4e1697231 100644 --- a/src/commands/base-command.ts +++ b/src/commands/base-command.ts @@ -11,10 +11,8 @@ import { resolveConfig } from '@netlify/config' import { Command, Help, Option } from 'commander' // @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'debu... Remove this comment to see the full error message import debug from 'debug' +import Enquirer from 'enquirer' import { findUp } from 'find-up' -import inquirer from 'inquirer' -// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'inqu... Remove this comment to see the full error message -import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt' import merge from 'lodash/merge.js' import { NetlifyAPI } from 'netlify' @@ -27,11 +25,11 @@ import { exit, getToken, log, - version, normalizeConfig, padLeft, pollForToken, sortOptions, + version, warn, } from '../utils/command-helpers.js' import { FeatureFlags } from '../utils/feature-flags.js' @@ -49,8 +47,6 @@ type Analytics = { payload?: Record } -// load the autocomplete plugin -inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt) /** Netlify CLI client id. Lives in bot@netlify.com */ // TODO: setup client for multiple environments const CLIENT_ID = 'd6f37de6614df7ae58664cfca524744d73807a377f5ee71f1a254f78412e3750' @@ -121,14 +117,14 @@ async function selectWorkspace(project: Project, filter?: string): Promise({ name: 'result', - // @ts-expect-error TS(2769) FIXME: No overload matches this call. type: 'autocomplete', message: 'Select the site you want to work with', - // @ts-expect-error TS(7006) FIXME: Parameter '_' implicitly has an 'any' type. - source: (/** @type {string} */ _, input = '') => - (project.workspace?.packages || []) + // @ts-expect-error Add enquirer types + // eslint-disable-next-line default-param-last + suggest: (input = '', _choices: any) => + (project.workspace?.packages || []) .filter((pkg) => pkg.path.includes(input)) .map((pkg) => ({ name: `${pkg.name ? `${chalk.bold(pkg.name)} ` : ''}${pkg.path} ${chalk.dim( @@ -270,6 +266,7 @@ export default class BaseCommand extends Command { return ( parentCommand?.commands .filter((cmd) => { + // eslint-disable-next-line no-underscore-dangle if ((cmd as any)._hidden) return false // the root command if (this.name() === 'netlify') { @@ -353,8 +350,10 @@ export default class BaseCommand extends Command { // Aliases // @ts-expect-error TS(2551) FIXME: Property '_aliases' does not exist on type 'Comman... Remove this comment to see the full error message + // eslint-disable-next-line no-underscore-dangle if (command._aliases.length !== 0) { // @ts-expect-error TS(2551) FIXME: Property '_aliases' does not exist on type 'Comman... Remove this comment to see the full error message + // eslint-disable-next-line no-underscore-dangle const aliases = command._aliases.map((alias) => formatItem(`${parentCommand.name()} ${alias}`, null, true)) output = [...output, chalk.bold('ALIASES'), formatHelpList(aliases), ''] } @@ -397,7 +396,7 @@ export default class BaseCommand extends Command { duration, status, }) - } catch {} + } catch { } if (error_ !== undefined) { error(error_ instanceof Error ? error_ : format(error_), { exit: false }) diff --git a/src/commands/deploy/deploy.ts b/src/commands/deploy/deploy.ts index 24c51fffb11..c9d795e47b3 100644 --- a/src/commands/deploy/deploy.ts +++ b/src/commands/deploy/deploy.ts @@ -4,7 +4,7 @@ import { basename, resolve } from 'path' import { runCoreSteps } from '@netlify/build' import { OptionValues } from 'commander' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import isEmpty from 'lodash/isEmpty.js' import isObject from 'lodash/isObject.js' import { parseAllHeaders } from 'netlify-headers-parser' @@ -98,13 +98,13 @@ const getDeployFolder = async ({ if (!deployFolder) { log('Please provide a publish directory (e.g. "public" or "dist" or "."):') - const { promptPath } = await inquirer.prompt([ + const { promptPath } = await Enquirer.prompt([ { type: 'input', name: 'promptPath', message: 'Publish directory', - default: '.', - filter: (input) => resolve(command.workingDir, input), + initial: '.', + result: (input) => resolve(command.workingDir, input), }, ]) deployFolder = promptPath as string @@ -255,12 +255,12 @@ const SYNC_FILE_LIMIT = 1e2 const prepareProductionDeploy = async ({ api, siteData }) => { if (isObject(siteData.published_deploy) && siteData.published_deploy.locked) { log(`\n${NETLIFYDEVERR} Deployments are "locked" for production context of this site\n`) - const { unlockChoice } = await inquirer.prompt([ + const { unlockChoice } = await Enquirer.prompt([ { type: 'confirm', name: 'unlockChoice', message: 'Would you like to "unlock" deployments for production context to proceed?', - default: false, + initial: false, }, ]) if (!unlockChoice) exit(0) @@ -364,7 +364,8 @@ const uploadDeployBlobs = async ({ silent: boolean siteId: string }) => { - const statusCb = silent ? () => {} : deployProgressCb() + // eslint-disable-next-line @typescript-eslint/no-empty-function, unicorn/empty-brace-spaces + const statusCb = silent ? () => { } : deployProgressCb() statusCb({ type: 'blobs-uploading', @@ -508,6 +509,7 @@ const runDeploy = async ({ fnDir: functionDirectories, functionsConfig, + // eslint-disable-next-line @typescript-eslint/no-empty-function statusCb: silent ? () => {} : deployProgressCb(), deployTimeout, syncFileLimit: SYNC_FILE_LIMIT, @@ -589,6 +591,7 @@ const bundleEdgeFunctions = async (options, command: BaseCommand) => { // eslint-disable-next-line n/prefer-global/process, unicorn/prefer-set-has const argv = process.argv.slice(2) const statusCb = + // eslint-disable-next-line @typescript-eslint/no-empty-function options.silent || argv.includes('--json') || argv.includes('--silent') ? () => {} : deployProgressCb() statusCb({ @@ -811,14 +814,14 @@ export const deploy = async (options: OptionValues, command: BaseCommand) => { const initializeOpts = [EXISTING_SITE, NEW_SITE] - const { initChoice } = await inquirer.prompt([ + const { initChoice } = await Enquirer.prompt([ { - type: 'list', - name: 'initChoice', - message: 'What would you like to do?', - choices: initializeOpts, - }, - ]) + type: 'select', + name: 'initChoice', + message: 'What would you like to do?', + choices: initializeOpts, + }, + ]) // create site or search for one if (initChoice === NEW_SITE) { // @ts-expect-error TS(2322) FIXME: Type 'undefined' is not assignable to type '{}'. diff --git a/src/commands/env/env-list.ts b/src/commands/env/env-list.ts index 278cb639bf0..191f81b6b75 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 { OptionValues } from 'commander' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import logUpdate from 'log-update' import { chalk, log, logJson } from '../../utils/command-helpers.js' @@ -99,17 +99,17 @@ export const envList = async (options: OptionValues, command: BaseCommand) => { } logUpdate(getTable({ environment, hideValues: true, scopesColumn: true })) - const { showValues } = await inquirer.prompt([ + const { showValues } = await Enquirer.prompt([ { type: 'confirm', name: 'showValues', message: 'Show values?', - default: false, + initial: false, }, ]) if (showValues) { - // since inquirer adds a prompt, we need to account for it when printing the table again + // since enquirer adds a prompt, we need to account for it when printing the table again log(ansiEscapes.eraseLines(3)) logUpdate(getTable({ environment, hideValues: false, scopesColumn: true })) log(`${chalk.cyan('?')} Show values? ${chalk.cyan('Yes')}`) diff --git a/src/commands/functions/functions-create.ts b/src/commands/functions/functions-create.ts index 1fcf7fe4781..3115e6f42fc 100644 --- a/src/commands/functions/functions-create.ts +++ b/src/commands/functions/functions-create.ts @@ -7,9 +7,9 @@ import process from 'process' import { fileURLToPath, pathToFileURL } from 'url' import { OptionValues } from 'commander' +import Enquirer from 'enquirer' import { findUp } from 'find-up' import fuzzy from 'fuzzy' -import inquirer from 'inquirer' import fetch from 'node-fetch' import ora from 'ora' @@ -57,17 +57,17 @@ const getNameFromArgs = async function (argumentName, options, defaultName) { return argumentName } - const { name } = await inquirer.prompt([ + const { name } = await Enquirer.prompt([ { - name: 'name', - message: 'Name your function:', - default: defaultName, - type: 'input', - validate: (val) => Boolean(val) && /^[\w.-]+$/i.test(val), - // make sure it is not undefined and is a valid filename. - // this has some nuance i have ignored, eg crossenv and i18n concerns - }, - ]) + name: 'name', + message: 'Name your function:', + initial: defaultName, + type: 'input', + validate: (val) => Boolean(val) && /^[\w.-]+$/i.test(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 } @@ -103,7 +103,7 @@ const filterRegistry = function (registry, input) { * @param {'edge' | 'serverless'} funcType */ // @ts-expect-error TS(7006) FIXME: Parameter 'lang' implicitly has an 'any' type. -const formatRegistryArrayForInquirer = async function (lang, funcType) { +const formatRegistryArrayForEnquirer = async function (lang, funcType) { const folders = await readdir(path.join(templatesDir, lang), { withFileTypes: true }) const imports = await Promise.all( @@ -116,13 +116,14 @@ const formatRegistryArrayForInquirer = async function (lang, funcType) { const template = await import(pathToFileURL(templatePath)) return template.default } catch { - // noop if import fails we don't break the whole inquirer + // noop if import fails we don't break the whole enquirer } }), ) const registry = imports .filter((template) => template?.functionType === funcType) .sort((templateA, templateB) => { + // eslint-disable-next-line @typescript-eslint/no-use-before-define const priorityDiff = (templateA.priority || DEFAULT_PRIORITY) - (templateB.priority || DEFAULT_PRIORITY) if (priorityDiff !== 0) { @@ -138,10 +139,10 @@ const formatRegistryArrayForInquirer = async function (lang, funcType) { .map((t) => { t.lang = lang return { - // confusing but this is the format inquirer wants + // confusing but this is the format enquirer wants name: `[${t.name}] ${t.description}`, value: t, - short: `${lang}-${t.name}`, + hint: `${lang}-${t.name}`, } }) return registry @@ -155,7 +156,7 @@ const formatRegistryArrayForInquirer = async function (lang, funcType) { // @ts-expect-error TS(7031) FIXME: Binding element 'languageFromFlag' implicitly has ... Remove this comment to see the full error message const pickTemplate = async function ({ language: languageFromFlag }, funcType) { const specialCommands = [ - new inquirer.Separator(), + { role: 'separator' }, { name: `Clone template from GitHub URL`, value: 'url', @@ -166,7 +167,7 @@ const pickTemplate = async function ({ language: languageFromFlag }, funcType) { value: 'report', short: 'gh-report', }, - new inquirer.Separator(), + { role: 'separator' }, ] let language = languageFromFlag @@ -177,44 +178,39 @@ const pickTemplate = async function ({ language: languageFromFlag }, funcType) { ? languages.filter((lang) => lang.value === 'javascript' || lang.value === 'typescript') : languages.filter(Boolean) - const { language: languageFromPrompt } = await inquirer.prompt({ + const { language: languageFromPrompt } = await Enquirer.prompt({ choices: langs, message: 'Select the language of your function', name: 'language', - type: 'list', + type: 'select', }) language = languageFromPrompt } - // @ts-expect-error TS(7034) FIXME: Variable 'templatesForLanguage' implicitly has typ... Remove this comment to see the full error message - let templatesForLanguage + let templatesForLanguage: any try { - templatesForLanguage = await formatRegistryArrayForInquirer(language, funcType) + templatesForLanguage = await formatRegistryArrayForEnquirer(language, funcType) } catch { throw error(`Invalid language: ${language}`) } - const { chosenTemplate } = await inquirer.prompt({ + const { chosenTemplate } = await Enquirer.prompt({ name: 'chosenTemplate', message: 'Pick a template', - // @ts-expect-error TS(2769) FIXME: No overload matches this call. type: 'autocomplete', - // @ts-expect-error TS(7006) FIXME: Parameter 'answersSoFar' implicitly has an 'any' t... Remove this comment to see the full error message - source(answersSoFar, input) { + // @ts-expect-error Add enquirer types + suggest: (input: string, answersSoFar: any) => { // if Edge Functions template, don't show url option - // @ts-expect-error TS(2339) FIXME: Property 'value' does not exist on type 'Separator... Remove this comment to see the full error message const edgeCommands = specialCommands.filter((val) => val.value !== 'url') const parsedSpecialCommands = funcType === 'edge' ? edgeCommands : specialCommands if (!input || input === '') { // show separators - // @ts-expect-error TS(7005) FIXME: Variable 'templatesForLanguage' implicitly has an ... Remove this comment to see the full error message return [...templatesForLanguage, ...parsedSpecialCommands] } // only show filtered results sorted by score - // @ts-expect-error TS(7005) FIXME: Variable 'templatesForLanguage' implicitly has an ... Remove this comment to see the full error message const answers = [...filterRegistry(templatesForLanguage, input), ...parsedSpecialCommands].sort( (answerA, answerB) => answerB.score - answerA.score, ) @@ -233,11 +229,11 @@ const selectTypeOfFunc = async () => { { name: 'Serverless function (Node/Go/Rust)', value: 'serverless' }, ] - const { functionType } = await inquirer.prompt([ + const { functionType } = await Enquirer.prompt([ { name: 'functionType', message: "Select the type of function you'd like to create", - type: 'list', + type: 'select', choices: functionTypes, }, ]) @@ -288,12 +284,12 @@ const promptFunctionsDirectory = async (command) => { error(`${NETLIFYDEVERR} No site id found, please run inside a site directory or \`netlify link\``) } - const { functionsDir } = await inquirer.prompt([ + const { functionsDir } = await Enquirer.prompt([ { type: 'input', name: 'functionsDir', message: 'Enter the path, relative to your site, where your functions should live:', - default: 'netlify/functions', + initial: 'netlify/functions', }, ]) @@ -395,7 +391,9 @@ const downloadFromURL = async function (command, options, argumentName, function default: { addons = [], onComplete }, } = await import(pathToFileURL(fnTemplateFile).href) + // eslint-disable-next-line @typescript-eslint/no-use-before-define await installAddons(command, addons, path.resolve(fnFolder)) + // eslint-disable-next-line @typescript-eslint/no-use-before-define await handleOnComplete({ command, onComplete }) // delete await unlink(fnTemplateFile) @@ -477,7 +475,7 @@ 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([ + const { chosenUrl } = await Enquirer.prompt([ { name: 'chosenUrl', message: 'URL to clone: ', @@ -510,6 +508,7 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun const name = await getNameFromArgs(argumentName, options, templateName) log(`${NETLIFYDEVLOG} Creating function ${chalk.cyan.inverse(name)}`) + // eslint-disable-next-line @typescript-eslint/no-use-before-define const functionPath = ensureFunctionPathIsOk(functionsDir, name) const vars = { name } @@ -526,6 +525,7 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun log(`${NETLIFYDEVLOG} ${chalk.greenBright('Created')} ${filePath}`) } + // eslint-disable-next-line @typescript-eslint/no-use-before-define fs.chmodSync(path.resolve(filePath), TEMPLATE_PERMISSIONS) if (filePath.includes('package.json')) { functionPackageJson = path.resolve(filePath) @@ -546,16 +546,19 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun } if (funcType === 'edge') { + // eslint-disable-next-line @typescript-eslint/no-use-before-define await registerEFInToml(name, command.netlify) } + // eslint-disable-next-line @typescript-eslint/no-use-before-define await installAddons(command, addons, path.resolve(functionPath)) + // eslint-disable-next-line @typescript-eslint/no-use-before-define await handleOnComplete({ command, onComplete }) log() log(chalk.greenBright(`Function created!`)) - if (lang == 'rust') { + if (lang === 'rust') { log( chalk.green( `Please note that Rust functions require setting the NETLIFY_EXPERIMENTAL_BUILD_RUST_SOURCE environment variable to 'true' on your site.`, @@ -624,12 +627,12 @@ const handleAddonDidInstall = async ({ addonCreated, addonDidInstall, command, f return } - const { confirmPostInstall } = await inquirer.prompt([ + const { confirmPostInstall } = await Enquirer.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, + initial: false, }, ]) @@ -701,12 +704,12 @@ const registerEFInToml = async (funcName, options) => { log(`${NETLIFYDEVLOG} \`${relConfigFilePath}\` file does not exist yet. Creating it...`) } - let { funcPath } = await inquirer.prompt([ + let { funcPath } = await Enquirer.prompt([ { type: 'input', name: 'funcPath', message: `What route do you want your edge function to be invoked on?`, - default: '/test', + initial: '/test', validate: (val) => Boolean(val), // Make sure route isn't undefined and is valid // Todo: add more validation? diff --git a/src/commands/functions/functions-invoke.ts b/src/commands/functions/functions-invoke.ts index 0ca0da575f0..6446c043499 100644 --- a/src/commands/functions/functions-invoke.ts +++ b/src/commands/functions/functions-invoke.ts @@ -3,7 +3,7 @@ import { createRequire } from 'module' import path from 'path' import { OptionValues } from 'commander' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import fetch from 'node-fetch' import { NETLIFYDEVWARN, chalk, error, exit } from '../../utils/command-helpers.js' @@ -97,6 +97,7 @@ const processPayloadFromFlag = function (payloadString, workingDir) { */ // @ts-expect-error TS(7006) FIXME: Parameter 'functions' implicitly has an 'any' type... Remove this comment to see the full error message const getNameFromArgs = async function (functions, options, argumentName) { + // eslint-disable-next-line @typescript-eslint/no-use-before-define const functionToTrigger = getFunctionToTrigger(options, argumentName) // @ts-expect-error TS(7031) FIXME: Binding element 'name' implicitly has an 'any' typ... Remove this comment to see the full error message const functionNames = functions.map(({ name }) => name) @@ -113,9 +114,9 @@ const getNameFromArgs = async function (functions, options, argumentName) { ) } - const { trigger } = await inquirer.prompt([ + const { trigger } = await Enquirer.prompt([ { - type: 'list', + type: 'select', message: 'Pick a function to trigger', name: 'trigger', choices: functionNames, diff --git a/src/commands/init/init.ts b/src/commands/init/init.ts index c104649ab5a..80d762e2713 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 Enquirer from 'enquirer' import isEmpty from 'lodash/isEmpty.js' import { chalk, exit, log } from '../../utils/command-helpers.js' @@ -122,9 +122,9 @@ git remote add origin https://github.com/YourUserName/RepoName.git const NEW_SITE_NO_GIT = 'Yes, create and deploy site manually' const NO_ABORT = 'No, I will connect this directory with GitHub first' - const { noGitRemoteChoice } = await inquirer.prompt([ + const { noGitRemoteChoice } = await Enquirer.prompt([ { - type: 'list', + type: 'select', name: 'noGitRemoteChoice', message: 'Do you want to create a Netlify site without a git repository?', choices: [NEW_SITE_NO_GIT, NO_ABORT], @@ -149,9 +149,9 @@ const createOrLinkSiteToRepo = async (command) => { const initializeOpts = [EXISTING_SITE, NEW_SITE] - const { initChoice } = await inquirer.prompt([ + const { initChoice } = await Enquirer.prompt([ { - type: 'list', + type: 'select', name: 'initChoice', message: 'What would you like to do?', choices: initializeOpts, diff --git a/src/commands/integration/deploy.ts b/src/commands/integration/deploy.ts index df2f74ce22d..4e762e02518 100644 --- a/src/commands/integration/deploy.ts +++ b/src/commands/integration/deploy.ts @@ -3,7 +3,7 @@ import { resolve } from 'path' import { env, exit } from 'process' import { OptionValues } from 'commander' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' // @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'js-y... Remove this comment to see the full error message import yaml from 'js-yaml' import fetch from 'node-fetch' @@ -121,12 +121,12 @@ function verifyRequiredFieldsAreInConfig(name, description, scopes, integrationL export async function registerIntegration(workingDir, siteId, accountId, localIntegrationConfig, token) { const { description, integrationLevel, name, scopes, slug } = localIntegrationConfig log(chalk.yellow(`An integration associated with the site ID ${siteId} is not registered.`)) - const registerPrompt = await inquirer.prompt([ + const registerPrompt = await Enquirer.prompt([ { type: 'confirm', name: 'registerIntegration', message: `Would you like to register this site as a private integration now?`, - default: false, + initial: false, }, ]) @@ -260,12 +260,12 @@ export async function updateIntegration( // @ts-expect-error TS(7005) FIXME: Variable 'localScopes' implicitly has an 'any[]' t... Remove this comment to see the full error message logScopeConfirmationMessage(localScopes, registeredIntegrationScopes) - const scopePrompt = await inquirer.prompt([ + const scopePrompt = await Enquirer.prompt([ { type: 'confirm', name: 'updateScopes', message: `Do you want to update the scopes?`, - default: false, + initial: false, }, ]) @@ -302,12 +302,12 @@ export async function updateIntegration( exit(1) } } else { - const useRegisteredScopesPrompt = await inquirer.prompt([ + const useRegisteredScopesPrompt = await Enquirer.prompt([ { type: 'confirm', name: 'useRegisteredScopes', message: `Do you want to save the scopes registered for your integration in your local configuration file?`, - default: false, + initial: false, }, ]) diff --git a/src/commands/link/link.ts b/src/commands/link/link.ts index 30889cb2525..c2f8cc74824 100644 --- a/src/commands/link/link.ts +++ b/src/commands/link/link.ts @@ -1,5 +1,5 @@ import { OptionValues } from 'commander' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import isEmpty from 'lodash/isEmpty.js' import { listSites } from '../../lib/api.js' @@ -38,9 +38,9 @@ const linkPrompt = async (command, options) => { log() log(`${chalk.cyanBright('netlify link')} will connect this folder to a site on Netlify`) log() - const { linkType } = await inquirer.prompt([ + const { linkType } = await Enquirer.prompt([ { - type: 'list', + type: 'select', name: 'linkType', message: 'How do you want to link this folder to a site?', choices: linkChoices, @@ -87,9 +87,9 @@ Run ${chalk.cyanBright('git remote -v')} to see a list of your git remotes.`) log(`Found ${matchingSites.length} matching sites!`) // Prompt which options - const { selectedSite } = await inquirer.prompt([ + const { selectedSite } = await Enquirer.prompt([ { - type: 'list', + type: 'select', name: 'selectedSite', message: 'Which site do you want to link?', // @ts-expect-error TS(7006) FIXME: Parameter 'matchingSite' implicitly has an 'any' t... Remove this comment to see the full error message @@ -108,7 +108,7 @@ Run ${chalk.cyanBright('git remote -v')} to see a list of your git remotes.`) } case SITE_NAME_PROMPT: { kind = 'byName' - const { searchTerm } = await inquirer.prompt([ + const { searchTerm } = await Enquirer.prompt([ { type: 'input', name: 'searchTerm', @@ -143,12 +143,12 @@ or run ${chalk.cyanBright('netlify sites:create')} to create a site.`) if (matchingSites.length > 1) { log(`Found ${matchingSites.length} matching sites!`) - const { selectedSite } = await inquirer.prompt([ + const { selectedSite } = await Enquirer.prompt([ { - type: 'list', + type: 'select', name: 'selectedSite', message: 'Which site do you want to link?', - paginated: true, + // paginated: true, // @ts-expect-error TS(7006) FIXME: Parameter 'matchingSite' implicitly has an 'any' t... Remove this comment to see the full error message choices: matchingSites.map((matchingSite) => ({ name: matchingSite.name, value: matchingSite })), }, @@ -180,12 +180,12 @@ or run ${chalk.cyanBright('netlify sites:create')} to create a site.`) error(`You don't have any sites yet. Run ${chalk.cyanBright('netlify sites:create')} to create a site.`) } - const { selectedSite } = await inquirer.prompt([ + const { selectedSite } = await Enquirer.prompt([ { - type: 'list', + type: 'select', name: 'selectedSite', message: 'Which site do you want to link?', - paginated: true, + // paginated: true, // @ts-expect-error TS(7006) FIXME: Parameter 'matchingSite' implicitly has an 'any' t... Remove this comment to see the full error message choices: sites.map((matchingSite) => ({ name: matchingSite.name, value: matchingSite })), }, @@ -198,7 +198,7 @@ or run ${chalk.cyanBright('netlify sites:create')} to create a site.`) } case SITE_ID_PROMPT: { kind = 'bySiteId' - const { siteId } = await inquirer.prompt([ + const { siteId } = await Enquirer.prompt([ { type: 'input', name: 'siteId', diff --git a/src/commands/logs/build.ts b/src/commands/logs/build.ts index 502d1b97d92..534ef527aac 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 Enquirer from 'enquirer' import { log, chalk } from '../../utils/command-helpers.js' import { getWebSocket } from '../../utils/websockets/index.js' @@ -48,9 +48,9 @@ export const logsBuild = async (options: OptionValues, command: BaseCommand) => let [deploy] = deploys if (deploys.length > 1) { - const { result } = await inquirer.prompt({ + const { result } = await Enquirer.prompt({ name: 'result', - type: 'list', + type: '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/logs/functions.ts b/src/commands/logs/functions.ts index dc84063bee2..958772f7346 100644 --- a/src/commands/logs/functions.ts +++ b/src/commands/logs/functions.ts @@ -1,5 +1,5 @@ import { OptionValues } from 'commander' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import { chalk, log } from '../../utils/command-helpers.js' import { getWebSocket } from '../../utils/websockets/index.js' @@ -49,9 +49,9 @@ export const logsFunction = async (functionName: string | undefined, options: Op if (functionName) { selectedFunction = functions.find((fn: any) => fn.n === functionName) } else { - const { result } = await inquirer.prompt({ + const { result } = await Enquirer.prompt({ name: 'result', - type: 'list', + type: 'select', message: 'Select a function', choices: functions.map((fn: any) => fn.n), }) diff --git a/src/commands/main.ts b/src/commands/main.ts index 451dd0307b3..e9159cef62c 100644 --- a/src/commands/main.ts +++ b/src/commands/main.ts @@ -1,10 +1,10 @@ import process from 'process' import { Option } from 'commander' +import Enquirer from 'enquirer' // @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'envi... Remove this comment to see the full error message import envinfo from 'envinfo' import { closest } from 'fastest-levenshtein' -import inquirer from 'inquirer' import { BANG, chalk, error, exit, log, NETLIFY_CYAN, USER_AGENT, warn } from '../utils/command-helpers.js' import execa from '../utils/execa.js' @@ -55,6 +55,7 @@ process.on('uncaughtException', async (err) => { { exit: false }, ) + // eslint-disable-next-line @typescript-eslint/no-use-before-define const systemInfo = await getSystemInfo() console.log(chalk.dim(err.stack || err)) @@ -153,11 +154,11 @@ const mainCommand = async function (options, command) { const suggestion = closest(command.args[0], allCommands) const applySuggestion = await new Promise((resolve) => { - const prompt = inquirer.prompt({ + const prompt = Enquirer.prompt({ type: 'confirm', name: 'suggestion', message: `Did you mean ${chalk.blue(suggestion)}`, - default: false, + initial: false, }) setTimeout(() => { diff --git a/src/commands/recipes/recipes.ts b/src/commands/recipes/recipes.ts index 40a18cc72e8..8f9aff50ecc 100644 --- a/src/commands/recipes/recipes.ts +++ b/src/commands/recipes/recipes.ts @@ -1,8 +1,8 @@ import { basename } from 'path' import { OptionValues } from 'commander' +import Enquirer from 'enquirer' import { closest } from 'fastest-levenshtein' -import inquirer from 'inquirer' import { NETLIFYDEVERR, chalk, log } from '../../utils/command-helpers.js' import BaseCommand from '../base-command.js' @@ -52,11 +52,11 @@ export const recipesCommand = async (recipeName: string, options: OptionValues, const recipeNames = recipes.map(({ name }) => name) const suggestion = closest(recipeName, recipeNames) const applySuggestion = await new Promise((resolve) => { - const prompt = inquirer.prompt({ + const prompt = Enquirer.prompt({ type: 'confirm', name: 'suggestion', message: `Did you mean ${chalk.blue(suggestion)}`, - default: false, + initial: false, }) setTimeout(() => { diff --git a/src/commands/sites/sites-create-template.ts b/src/commands/sites/sites-create-template.ts index ff2747f1086..b454a760ec6 100644 --- a/src/commands/sites/sites-create-template.ts +++ b/src/commands/sites/sites-create-template.ts @@ -1,5 +1,5 @@ import { OptionValues } from 'commander' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import pick from 'lodash/pick.js' // @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'pars... Remove this comment to see the full error message import parseGitHubUrl from 'parse-github-url' @@ -50,9 +50,9 @@ const getTemplateName = async ({ ghToken, options, repository }) => { log(`Choose one of our starter templates. Netlify will create a new repo for this template in your GitHub account.`) - const { templateName } = await inquirer.prompt([ + const { templateName } = await Enquirer.prompt([ { - type: 'list', + type: 'select', name: 'templateName', message: 'Template:', // @ts-expect-error TS(7006) FIXME: Parameter 'template' implicitly has an 'any' type. @@ -100,9 +100,9 @@ export const sitesCreateTemplate = async (repository: string, options: OptionVal let { accountSlug } = options if (!accountSlug) { - const { accountSlug: accountSlugInput } = await inquirer.prompt([ + const { accountSlug: accountSlugInput } = await Enquirer.prompt([ { - type: 'list', + type: 'select', name: 'accountSlug', message: 'Team:', // @ts-expect-error TS(7006) FIXME: Parameter 'account' implicitly has an 'any' type. @@ -205,11 +205,11 @@ export const sitesCreateTemplate = async (repository: string, options: OptionVal siteUrl, }) - const { cloneConfirm } = await inquirer.prompt({ + const { cloneConfirm }= await Enquirer.prompt({ type: 'confirm', name: 'cloneConfirm', message: `Do you want to clone the repository?`, - default: true, + initial: true, }) if (cloneConfirm) { log() diff --git a/src/commands/sites/sites-create.ts b/src/commands/sites/sites-create.ts index c7a21f3d81a..93fc8c85e08 100644 --- a/src/commands/sites/sites-create.ts +++ b/src/commands/sites/sites-create.ts @@ -1,5 +1,5 @@ import { OptionValues } from 'commander' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import pick from 'lodash/pick.js' import prettyjson from 'prettyjson' @@ -13,12 +13,13 @@ import { link } from '../link/link.js' // @ts-expect-error TS(7006) FIXME: Parameter 'name' implicitly has an 'any' type. export const getSiteNameInput = async (name) => { if (!name) { - const { name: nameInput } = await inquirer.prompt([ + const { name: nameInput } = await Enquirer.prompt([ { type: 'input', name: 'name', message: 'Site name (leave blank for a random name; you can change it later):', validate: (input) => + // @ts-expect-error ignore by now /^[a-zA-Z\d-]+$/.test(input || undefined) || 'Only alphanumeric characters and hyphens are allowed', }, ]) @@ -37,9 +38,9 @@ export const sitesCreate = async (options: OptionValues, command: BaseCommand) = let { accountSlug } = options if (!accountSlug) { - const { accountSlug: accountSlugInput } = await inquirer.prompt([ + const { accountSlug: accountSlugInput } = await Enquirer.prompt([ { - type: 'list', + type: 'select', name: 'accountSlug', message: 'Team:', // @ts-expect-error TS(7006) FIXME: Parameter 'account' implicitly has an 'any' type. diff --git a/src/commands/sites/sites-delete.ts b/src/commands/sites/sites-delete.ts index 70956c893c3..19522e161e4 100644 --- a/src/commands/sites/sites-delete.ts +++ b/src/commands/sites/sites-delete.ts @@ -1,5 +1,5 @@ import { OptionValues } from 'commander' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import { chalk, error, exit, log } from '../../utils/command-helpers.js' import BaseCommand from '../base-command.js' @@ -37,11 +37,11 @@ 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({ + const { wantsToDelete } = await Enquirer.prompt({ type: 'confirm', name: 'wantsToDelete', message: `WARNING: Are you sure you want to delete the "${siteData.name}" site?`, - default: false, + initial: false, }) log() if (!wantsToDelete) { @@ -58,11 +58,11 @@ export const sitesDelete = async (siteId: string, options: OptionValues, command log() log(`Verify this siteID "${siteId}" supplied is correct and proceed.`) log('To skip this prompt, pass a --force flag to the delete command') - const { wantsToDelete } = await inquirer.prompt({ + const { wantsToDelete } = await Enquirer.prompt({ type: 'confirm', name: 'wantsToDelete', message: `Verify & Proceed with deletion of site "${siteId}"?`, - default: false, + initial: false, }) if (!wantsToDelete) { exit() diff --git a/src/commands/switch/switch.ts b/src/commands/switch/switch.ts index b1052278ab5..dbdaecee147 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 Enquirer from 'enquirer' import { chalk, log } from '../../utils/command-helpers.js' import BaseCommand from '../base-command.js' @@ -15,13 +15,13 @@ export const switchCommand = async (options: OptionValues, command: BaseCommand) {}, ) - const { accountSwitchChoice } = await inquirer.prompt([ + const { accountSwitchChoice } = await Enquirer.prompt([ { - type: 'list', + type: 'select', name: 'accountSwitchChoice', message: 'Please select the account you want to use:', // @ts-expect-error TS(2769) FIXME: No overload matches this call. - choices: [...Object.entries(availableUsersChoices).map(([, val]) => val), LOGIN_NEW], + choices: [...Object.entries(availableUsersChoices).map(([, val]) => val as any), LOGIN_NEW], }, ]) diff --git a/src/lib/edge-functions/editor-helper.ts b/src/lib/edge-functions/editor-helper.ts index e656000931a..8e0e8f205b7 100644 --- a/src/lib/edge-functions/editor-helper.ts +++ b/src/lib/edge-functions/editor-helper.ts @@ -1,6 +1,6 @@ import { env } from 'process' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import { runRecipe } from '../../commands/recipes/recipes.js' @@ -23,11 +23,11 @@ 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({ + const { confirm } = await Enquirer.prompt({ type: 'confirm', name: 'confirm', message, - default: true, + initial: true, }) if (!confirm) { diff --git a/src/recipes/blobs-migrate/index.ts b/src/recipes/blobs-migrate/index.ts index 1179d530856..6a3cd86de75 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 Enquirer from 'enquirer' import pMap from 'p-map' import BaseCommand from '../../commands/base-command.js' @@ -47,11 +47,11 @@ export const run = async ({ args, command }: Options) => { const { stores } = await listStores(clientOptions) if (stores.includes(storeName)) { - const { confirmExistingStore } = await inquirer.prompt({ + const { confirmExistingStore } = await Enquirer.prompt({ type: 'confirm', name: 'confirmExistingStore', 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, + initial: false, }) if (!confirmExistingStore) { @@ -59,11 +59,11 @@ export const run = async ({ args, command }: Options) => { } } - const { confirmMigration } = await inquirer.prompt({ + const { confirmMigration } = await Enquirer.prompt({ type: 'confirm', name: 'confirmMigration', message: `You're about to migrate the store '${storeName}' with ${blobs.length} blobs. Do you want to proceed?`, - default: true, + initial: true, }) if (!confirmMigration) { diff --git a/src/recipes/vscode/index.ts b/src/recipes/vscode/index.ts index 3fd7be1a96b..fac016f3dd1 100644 --- a/src/recipes/vscode/index.ts +++ b/src/recipes/vscode/index.ts @@ -1,8 +1,8 @@ import { join } from 'path' import { DenoBridge } from '@netlify/edge-bundler' +import Enquirer from 'enquirer' import execa from 'execa' -import inquirer from 'inquirer' import { NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, error, log } from '../../utils/command-helpers.js' @@ -17,11 +17,11 @@ 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({ + return Enquirer.prompt({ type: 'confirm', name: 'confirm', message, - default: true, + initial: true, }) } @@ -55,11 +55,11 @@ 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({ + return Enquirer.prompt({ type: 'confirm', name: 'confirm', message, - default: true, + initial: true, }) } diff --git a/src/utils/addons/prompts.ts b/src/utils/addons/prompts.ts index 967aa2aff1e..597a04ff6be 100644 --- a/src/utils/addons/prompts.ts +++ b/src/utils/addons/prompts.ts @@ -31,7 +31,7 @@ export default function generatePrompts(settings) { // if current stage value set show as default if (configValues[key]) { // @ts-expect-error TS(2339) FIXME: Property 'default' does not exist on type '{ type:... Remove this comment to see the full error message - prompt.default = configValues[key] + prompt.initial = configValues[key] } } else if (typeof setting === 'boolean') { prompt = { @@ -45,7 +45,9 @@ export default function generatePrompts(settings) { // For future use. Once UX is decided // const defaultValidation = (setting.required) ? validateRequired : noValidate + // eslint-disable-next-line @typescript-eslint/no-use-before-define const defaultValidation = noValidate + // eslint-disable-next-line @typescript-eslint/no-use-before-define const validateFunction = setting.pattern ? validate(setting.pattern) : defaultValidation const isRequiredText = setting.required ? ` (${chalk.yellow('required')})` : '' if (setting.type === 'string' || /string/.test(setting.type)) { @@ -58,11 +60,11 @@ export default function generatePrompts(settings) { // if value previously set show it if (configValues[key]) { // @ts-expect-error TS(2339) FIXME: Property 'default' does not exist on type '{ type:... Remove this comment to see the full error message - prompt.default = configValues[key] + prompt.initial = configValues[key] // else show default value if provided } else if (setting.default) { // @ts-expect-error TS(2339) FIXME: Property 'default' does not exist on type '{ type:... Remove this comment to see the full error message - prompt.default = setting.default + prompt.initial = setting.default } return prompt } diff --git a/src/utils/build-info.ts b/src/utils/build-info.ts index deb9d62d6f4..b22653421dc 100644 --- a/src/utils/build-info.ts +++ b/src/utils/build-info.ts @@ -1,33 +1,34 @@ +/* eslint-disable default-param-last */ import { Settings } from '@netlify/build-info' import { isCI } from 'ci-info' +import Enquirer from 'enquirer' import fuzzy from 'fuzzy' -import inquirer from 'inquirer' import BaseCommand from '../commands/base-command.js' import { chalk, log } from './command-helpers.js' /** - * Filters the inquirer settings based on the input + * Filters the enquirer settings based on the input */ const filterSettings = function ( - scriptInquirerOptions: ReturnType, + scriptEnquirerOptions: ReturnType, input: string, ) { - const filterOptions = scriptInquirerOptions.map((scriptInquirerOption) => scriptInquirerOption.name) + const filterOptions = scriptEnquirerOptions.map((scriptEnquirerOption) => scriptEnquirerOption.name) // TODO: remove once https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1394 is fixed // eslint-disable-next-line unicorn/no-array-method-this-argument const filteredSettings = fuzzy.filter(input, filterOptions) const filteredSettingNames = new Set( filteredSettings.map((filteredSetting) => (input ? filteredSetting.string : filteredSetting)), ) - return scriptInquirerOptions.filter((t) => filteredSettingNames.has(t.name)) + return scriptEnquirerOptions.filter((t) => filteredSettingNames.has(t.name)) } /** - * Formats the settings to present it as an array for the inquirer input so that it can choose one + * Formats the settings to present it as an array for the enquirer input so that it can choose one */ -const formatSettingsArrForInquirer = function (settings: Settings[], type = 'dev') { +const formatSettingsArrForEnquirer = function (settings: Settings[], type = 'dev') { return settings.map((setting) => { const cmd = type === 'dev' ? setting.devCommand : setting.buildCommand return { @@ -96,16 +97,17 @@ export const detectFrameworkSettings = async ( } // multiple matching detectors, make the user choose - const scriptInquirerOptions = formatSettingsArrForInquirer(settings, type) - const { chosenSettings } = await inquirer.prompt<{ chosenSettings: Settings }>({ + const scriptEnquirerOptions = formatSettingsArrForEnquirer(settings, type) + const { chosenSettings } = await Enquirer.prompt<{ chosenSettings: Settings }>({ name: 'chosenSettings', message: `Multiple possible ${type} commands found`, - // @ts-expect-error is not known by the types as it uses the autocomplete plugin type: 'autocomplete', - source(_: string, input = '') { - if (!input) return scriptInquirerOptions + // @ts-expect-error Add enquirer types + // eslint-disable-next-line @typescript-eslint/no-unused-vars + suggest(input = '', _choices: any) { + if (!input) return scriptEnquirerOptions // only show filtered results - return filterSettings(scriptInquirerOptions, input) + return filterSettings(scriptEnquirerOptions, input) }, }) diff --git a/src/utils/gh-auth.ts b/src/utils/gh-auth.ts index 2e6bce40f5b..9c39759cc69 100644 --- a/src/utils/gh-auth.ts +++ b/src/utils/gh-auth.ts @@ -3,8 +3,8 @@ import http from 'http' import process from 'process' import { Octokit } from '@octokit/rest' +import Enquirer from 'enquirer' import getPort from 'get-port' -import inquirer from 'inquirer' import { log } from './command-helpers.js' import createDeferred from './create-deferred.js' @@ -25,9 +25,9 @@ const promptForAuthMethod = async () => { const authChoiceToken = 'Authorize with a GitHub personal access token' const authChoices = [authChoiceNetlify, authChoiceToken] - const { authMethod } = await inquirer.prompt([ + const { authMethod } = await Enquirer.prompt([ { - type: 'list', + type: 'select', name: 'authMethod', message: 'Netlify CLI needs access to your GitHub account to configure Webhooks and Deploy Keys. ' + @@ -56,7 +56,7 @@ export const authWithNetlify = async () => { res.end( `${ "Logged in" + - "

Logged in

You're now logged into Netlify CLI with your " + "

Logged in

You're now logged into Netlify CLI with your " }${parameters.get('provider')} credentials. Please close this window.

`, ) server.close() @@ -88,12 +88,12 @@ export const authWithNetlify = async () => { } const getPersonalAccessToken = async () => { - const { token } = await inquirer.prompt([ + const { token } = await Enquirer.prompt([ { type: 'input', name: 'token', message: 'Your GitHub personal access token:', - filter: (input) => input.trim(), + result: (input) => input.trim(), }, ]) diff --git a/src/utils/init/config-manual.ts b/src/utils/init/config-manual.ts index 7019a705e83..e24949f2e0a 100644 --- a/src/utils/init/config-manual.ts +++ b/src/utils/init/config-manual.ts @@ -1,4 +1,4 @@ -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import { exit, log } from '../command-helpers.js' @@ -14,12 +14,12 @@ const addDeployKey = async (deployKey) => { log('\nGive this Netlify SSH public key access to your repository:\n') log(`\n${deployKey.public_key}\n\n`) - const { sshKeyAdded } = await inquirer.prompt([ + const { sshKeyAdded } = await Enquirer.prompt([ { type: 'confirm', name: 'sshKeyAdded', message: 'Continue?', - default: true, + initial: true, }, ]) @@ -35,15 +35,16 @@ const addDeployKey = async (deployKey) => { */ // @ts-expect-error TS(7031) FIXME: Binding element 'repoData' implicitly has an 'any'... Remove this comment to see the full error message const getRepoPath = async ({ repoData }) => { - const { repoPath } = await inquirer.prompt([ + const { repoPath } = await Enquirer.prompt([ { type: 'input', name: 'repoPath', message: 'The SSH URL of the remote git repo:', - default: repoData.url, + initial: repoData.url, /** * @param {string} url */ + // eslint-disable-next-line @typescript-eslint/no-use-before-define validate: (url) => SSH_URL_REGEXP.test(url) || 'The URL provided does not use the SSH protocol', }, ]) @@ -59,12 +60,12 @@ const getRepoPath = async ({ repoData }) => { const addDeployHook = async (deployHook) => { log('\nConfigure the following webhook for your repository:\n') log(`\n${deployHook}\n\n`) - const { deployHookAdded } = await inquirer.prompt([ + const { deployHookAdded } = await Enquirer.prompt([ { type: 'confirm', name: 'deployHookAdded', message: 'Continue?', - default: true, + initial: true, }, ]) diff --git a/src/utils/init/utils.ts b/src/utils/init/utils.ts index ecbe67a5671..d5ba62dc4e5 100644 --- a/src/utils/init/utils.ts +++ b/src/utils/init/utils.ts @@ -4,7 +4,7 @@ import path from 'path' import { NetlifyConfig } from '@netlify/build' import { Settings } from '@netlify/build-info' import cleanDeep from 'clean-deep' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import BaseCommand from '../../commands/base-command.js' import { $TSFixMe } from '../../commands/types.js' @@ -69,25 +69,24 @@ const normalizeSettings = (settings: Partial, config: NetlifyConfig, c const getPromptInputs = ({ defaultBaseDir, defaultBuildCmd, defaultBuildDir }) => { const inputs = [ defaultBaseDir !== undefined && - defaultBaseDir !== '' && { - type: 'input', - name: 'baseDir', - message: 'Base directory `(blank for current dir):', - default: defaultBaseDir, - }, + defaultBaseDir !== '' && { + type: 'input', + name: 'baseDir', + message: 'Base directory `(blank for current dir):', + initial: defaultBaseDir, + }, { type: 'input', name: 'buildCmd', message: 'Your build command (hugo build/yarn run build/etc):', - // @ts-expect-error TS(7006) FIXME: Parameter 'val' implicitly has an 'any' type. - filter: (val) => (val === '' ? '# no build command' : val), - default: defaultBuildCmd, + result: (val: string) => (val === '' ? '# no build command' : val), + initial: defaultBuildCmd, }, { type: 'input', name: 'buildDir', message: 'Directory to deploy (blank for current dir):', - default: defaultBuildDir, + initial: defaultBuildDir, }, ].filter(Boolean) @@ -107,12 +106,13 @@ export const getBuildSettings = async ({ command, config }: { command: BaseComma log() } - const { baseDir, buildCmd, buildDir } = await inquirer.prompt( + const { baseDir, buildCmd, buildDir } = await Enquirer.prompt( getPromptInputs({ defaultBaseDir, defaultBuildCmd, defaultBuildDir, - }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + }) as any, ) // @ts-expect-error TS(7006) FIXME: Parameter 'plugin' implicitly has an 'any' type. @@ -179,12 +179,12 @@ export const saveNetlifyToml = async ({ return } - const { makeNetlifyTOML } = await inquirer.prompt([ + const { makeNetlifyTOML } = await Enquirer.prompt([ { type: 'confirm', name: 'makeNetlifyTOML', message: 'No netlify.toml detected. Would you like to create one with these build settings?', - default: true, + initial: true, }, ]) if (makeNetlifyTOML) { diff --git a/tests/integration/commands/logs/functions.test.ts b/tests/integration/commands/logs/functions.test.ts index 278dba7423f..8c544ecc64f 100644 --- a/tests/integration/commands/logs/functions.test.ts +++ b/tests/integration/commands/logs/functions.test.ts @@ -1,3 +1,5 @@ +import process from 'node:process' + import { Mock, afterEach, beforeEach, describe, expect, test, vi } from 'vitest' import BaseCommand from '../../../../src/commands/base-command.js' @@ -20,7 +22,7 @@ vi.mock('../../../../src/utils/command-helpers.js', async () => { } }) -vi.mock('inquirer', () => ({ +vi.mock('enquirer', () => ({ default: { prompt: vi.fn().mockResolvedValue({ result: 'cool-function' }), registerPrompt: vi.fn(), @@ -55,7 +57,9 @@ const routes = [ response: { functions: [ { + // eslint-disable-next-line id-length n: 'cool-function', + // eslint-disable-next-line id-length a: 'account', oid: 'function-id', }, @@ -77,6 +81,7 @@ describe('logs:function command', () => { createLogsFunctionCommand(program) }) + // eslint-disable-next-line no-empty-pattern test('should setup the functions stream correctly', async ({}) => { const { apiUrl } = await startMockApi({ routes }) const spyWebsocket = getWebSocket as unknown as Mock @@ -96,6 +101,7 @@ describe('logs:function command', () => { expect(spyOn).toHaveBeenCalledTimes(4) }) + // eslint-disable-next-line no-empty-pattern test('should send the correct payload to the websocket', async ({}) => { const { apiUrl } = await startMockApi({ routes }) const spyWebsocket = getWebSocket as unknown as Mock @@ -114,10 +120,12 @@ describe('logs:function command', () => { const setupCall = spyOn.mock.calls.find((args) => args[0] === 'open') expect(setupCall).toBeDefined() + // eslint-disable-next-line prefer-destructuring const openCallback = setupCall[1] openCallback() expect(spySend).toHaveBeenCalledOnce() + // eslint-disable-next-line prefer-destructuring const call = spySend.mock.calls[0] const [message] = call const body = JSON.parse(message) @@ -128,6 +136,7 @@ describe('logs:function command', () => { expect(body.access_token).toEqual(env.NETLIFY_AUTH_TOKEN) }) + // eslint-disable-next-line no-empty-pattern test('should print only specified log levels', async ({}) => { const { apiUrl } = await startMockApi({ routes }) const spyWebsocket = getWebSocket as unknown as Mock @@ -144,6 +153,7 @@ describe('logs:function command', () => { await program.parseAsync(['', '', 'logs:function', '--level', 'info']) const messageCallback = spyOn.mock.calls.find((args) => args[0] === 'message') + // eslint-disable-next-line prefer-destructuring const messageCallbackFunc = messageCallback[1] const mockInfoData = { level: LOG_LEVELS.INFO, @@ -160,6 +170,7 @@ describe('logs:function command', () => { expect(spyLog).toHaveBeenCalledTimes(1) }) + // eslint-disable-next-line no-empty-pattern test('should print all the log levels', async ({}) => { const { apiUrl } = await startMockApi({ routes }) const spyWebsocket = getWebSocket as unknown as Mock @@ -176,6 +187,7 @@ describe('logs:function command', () => { await program.parseAsync(['', '', 'logs:function']) const messageCallback = spyOn.mock.calls.find((args) => args[0] === 'message') + // eslint-disable-next-line prefer-destructuring const messageCallbackFunc = messageCallback[1] const mockInfoData = { level: LOG_LEVELS.INFO, diff --git a/tests/integration/commands/recipes/__snapshots__/recipes.test.js.snap b/tests/integration/commands/recipes/__snapshots__/recipes.test.js.snap index 5f69add5d76..6ddd2eb3aed 100644 --- a/tests/integration/commands/recipes/__snapshots__/recipes.test.js.snap +++ b/tests/integration/commands/recipes/__snapshots__/recipes.test.js.snap @@ -10,8 +10,3 @@ exports[`commands/recipes > Shows a list of all the available recipes 1`] = ` | vscode | Create VS Code settings for an optimal experience with Netlify projects | '-----------------------------------------------------------------------------------------'" `; - -exports[`commands/recipes > Suggests closest matching recipe on typo 1`] = ` -"◈ vsc is not a valid recipe name. -? Did you mean vscode (y/N) " -`; diff --git a/tests/integration/commands/sites/sites.test.ts b/tests/integration/commands/sites/sites.test.ts index f0dfbc0a605..2e6bb3c335f 100644 --- a/tests/integration/commands/sites/sites.test.ts +++ b/tests/integration/commands/sites/sites.test.ts @@ -1,6 +1,6 @@ import process from 'process' -import inquirer from 'inquirer' +import Enquirer from 'enquirer' import { render } from 'prettyjson' import { afterAll, beforeEach, describe, expect, test, vi } from 'vitest' @@ -55,7 +55,7 @@ vi.mock('prettyjson', async () => { } }) -vi.spyOn(inquirer, 'prompt').mockImplementation(() => Promise.resolve({ accountSlug: 'test-account' })) +vi.spyOn(Enquirer, 'prompt').mockImplementation(() => Promise.resolve({ accountSlug: 'test-account' })) const siteInfo = { admin_url: 'https://app.netlify.com/sites/site-name/overview', From b995174bcc0ae1c78e2acc40f7868c5e5cb1293e Mon Sep 17 00:00:00 2001 From: Angel Mendez Cano Date: Mon, 8 Jul 2024 09:56:15 -0600 Subject: [PATCH 3/3] style: fix style issues --- src/commands/addons/addons-config.ts | 3 +++ src/commands/base-command.ts | 10 +++++----- src/recipes/blobs-migrate/index.ts | 2 ++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/commands/addons/addons-config.ts b/src/commands/addons/addons-config.ts index 00d197c2668..0cb8b18393b 100644 --- a/src/commands/addons/addons-config.ts +++ b/src/commands/addons/addons-config.ts @@ -88,6 +88,7 @@ export const addonsConfig = async (addonName: string, options: OptionValues, com return false } + // eslint-disable-next-line @typescript-eslint/no-explicit-any const updatePrompt = await Enquirer.prompt([ { type: 'confirm', @@ -112,6 +113,7 @@ export const addonsConfig = async (addonName: string, options: OptionValues, com configValues: currentConfig, }) // TODO: Fix type argument + // eslint-disable-next-line @typescript-eslint/no-explicit-any const userInput = await Enquirer.prompt(prompts as any) // Merge user input with the flags specified const newConfig = updateConfigValues(manifest.config, currentConfig, userInput) @@ -135,6 +137,7 @@ export const addonsConfig = async (addonName: string, options: OptionValues, com }) log() + // eslint-disable-next-line @typescript-eslint/no-explicit-any const confirmPrompt = await Enquirer.prompt([ { type: 'confirm', diff --git a/src/commands/base-command.ts b/src/commands/base-command.ts index 7b4e1697231..eb76ee3a939 100644 --- a/src/commands/base-command.ts +++ b/src/commands/base-command.ts @@ -1,5 +1,3 @@ -import { isCI } from 'ci-info' - import { existsSync } from 'fs' import { join, relative, resolve } from 'path' import process from 'process' @@ -8,6 +6,7 @@ import { format } from 'util' import { DefaultLogger, Project } from '@netlify/build-info' import { NodeFS, NoopLogger } from '@netlify/build-info/node' import { resolveConfig } from '@netlify/config' +import { isCI } from 'ci-info' import { Command, Help, Option } from 'commander' // @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module 'debu... Remove this comment to see the full error message import debug from 'debug' @@ -117,12 +116,13 @@ async function selectWorkspace(project: Project, filter?: string): Promise({ name: 'result', type: 'autocomplete', message: 'Select the site you want to work with', // @ts-expect-error Add enquirer types - // eslint-disable-next-line default-param-last + // eslint-disable-next-line default-param-last, @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any suggest: (input = '', _choices: any) => (project.workspace?.packages || []) .filter((pkg) => pkg.path.includes(input)) @@ -266,7 +266,7 @@ export default class BaseCommand extends Command { return ( parentCommand?.commands .filter((cmd) => { - // eslint-disable-next-line no-underscore-dangle + // eslint-disable-next-line no-underscore-dangle, @typescript-eslint/no-explicit-any if ((cmd as any)._hidden) return false // the root command if (this.name() === 'netlify') { @@ -396,7 +396,7 @@ export default class BaseCommand extends Command { duration, status, }) - } catch { } + } catch {} if (error_ !== undefined) { error(error_ instanceof Error ? error_ : format(error_), { exit: false }) diff --git a/src/recipes/blobs-migrate/index.ts b/src/recipes/blobs-migrate/index.ts index 6a3cd86de75..36f27ccb26d 100644 --- a/src/recipes/blobs-migrate/index.ts +++ b/src/recipes/blobs-migrate/index.ts @@ -47,6 +47,7 @@ export const run = async ({ args, command }: Options) => { const { stores } = await listStores(clientOptions) if (stores.includes(storeName)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const { confirmExistingStore } = await Enquirer.prompt({ type: 'confirm', name: 'confirmExistingStore', @@ -59,6 +60,7 @@ export const run = async ({ args, command }: Options) => { } } + // eslint-disable-next-line @typescript-eslint/no-explicit-any const { confirmMigration } = await Enquirer.prompt({ type: 'confirm', name: 'confirmMigration',