enquirer icon indicating copy to clipboard operation
enquirer copied to clipboard

is enquirer a direct swap for inquirer?

Open jonschlinkert opened this issue 7 years ago • 20 comments

Originally posted by @jondot in https://github.com/enquirer/enquirer/issues/25#issuecomment-437651726

jonschlinkert avatar Nov 11 '18 09:11 jonschlinkert

@jondot, it depends on how strictly you mean "direct swap". I'll rephrase in a couple ways.

Can Enquirer do everything that Inquirer does?

Yes, and more.

Does Enquire have all of the same promps as Inquirer?

No, Enquirer does no have the expand prompt. But this would be really easy to create as a custom prompt. I'd consider adding it if there is interest, but I couldn't really think of a reason why someone would use it.

Does Enquirer use all of the same "question" property names as Inquirer?

No. Enquirer supports all of the same features, but we use different names for a couple of things.

Inquirer Enquirer
default initial
filter result
transformer format
when skip (opposite of when)
pageSize limit
suffix N/A

Regarding suffix, Enquirer is much more customizable than Inquirer. You can customize header, prefix, message, separator, hint, error, and footer for the prompt. Additionally, each choice may have a custom pointer (the "arrow" that signifies which choice is currently focused), indicator (checkbox, radio button, etc. which indicates if the choice is enable/checked), message, hint or error message.

Hope this helps. I'm still working on docs.

jonschlinkert avatar Nov 11 '18 09:11 jonschlinkert

Also forgot to mention, I think choice objects are a little different. As I recall, Inquirer supports name and value (name is displayed in the terminal, value is returned on answer).

Enquirer supports name, value and message. This allows you to use name as the "key", message to display in the terminal, and value is returned in the answer.

jonschlinkert avatar Nov 11 '18 10:11 jonschlinkert

I think we should add a "migration guide" to help who switches from Inquirer to Enquirer.

g-plane avatar Nov 11 '18 10:11 g-plane

Thanks all. Currently for the Hygen test suite swapping inquirer with enquirer was replacing just a single character (i replaced with e), and all tests keep passing :)

After reading this there may be other users out there already using more elaborate features of inquirer which will break once they hit the version that has the swap. In this case I'll have to introduce a 2.0.0 version and a breaking API alert (but I think it's worth it for the performance gains).

jondot avatar Nov 11 '18 12:11 jondot

I have published a package for migrating from Inquirer to Enquirer: GitHub link: https://github.com/g-plane/enquirer-compat npm link: https://npm.im/enquirer-compat

Can I join the "enquirer" organization? If so, I will transfer that repository to this organization.

g-plane avatar Nov 12 '18 09:11 g-plane

@g-plane great! I'll review in more depth and add you to the org later today. A couple of things that I noticed:

  • Enquirer's equivalent to the checkbox prompt is multiselect
  • Enquirer's equivalent to the pageSize option is limit

jonschlinkert avatar Nov 12 '18 14:11 jonschlinkert

Thanks! I will update the readme later.

g-plane avatar Nov 12 '18 14:11 g-plane

I have published a package for migrating from Inquirer to Enquirer: GitHub link: g-plane/enquirer-compat npm link: npm.im/enquirer-compat

Can I join the "enquirer" organization? If so, I will transfer that repository to this organization.

I have released a new version of enquirer-compat, and now it supports the pageSize option and it can detect the checkbox prompt (convert it to multiselect).

g-plane avatar Nov 13 '18 11:11 g-plane

@g-plane that's great! I looked over the package, nice work. however, my suggestion is that you only modify the "question" object and return that, so the user can still use Enquirer the same way.

For example, something like this:

function compat(questions) {
  let arr = [].concat(questions || []);
  let result = [];
  for (let question of arr) {
    result.push(normalize(question));
  }
  return result;
}

function normalize(question) {
  let options = { ...question };

  for (let key of Object.keys(question)) {
    let value = question[key];
    if (key === 'when' && typeof value === 'function') {
      options.skip = async(...args) => !(await value(...args));
      delete options[key];
      continue;
    }
    // etc ...
  }
  return options;
}

Which can then be used like this:

const compat = require('enquirer-compat');
const { prompt } = require('enquirer');

const questions = [
  {
    type: 'select',
    message: 'What is your favorite color?',
    choices: ['Beige', 'Alabaster', 'Cream', 'Off-white']
  }
];

prompt(compat(questions))
  .then(answers => console.log('Answers:', answers))
  .catch(console.error);

The advantage of this is that you only need to update the options object.

edit: I wasn't thinking about the prompt properties, just the options. I'll need to think about that part. I think we need to find a way to let the user control the instances.

jonschlinkert avatar Nov 13 '18 12:11 jonschlinkert

@g-plane sent you an invite. (you might get two, I accidentally invited you to this repo only first, then I revoked that and reinvited you to the org).

jonschlinkert avatar Nov 13 '18 12:11 jonschlinkert

@jonschlinkert Thanks a lot! I have a question: does Enquirer support a feature like Inquirer that the validate, result and format function can receive the current answers object as a parameter?

g-plane avatar Nov 13 '18 12:11 g-plane

Repo was transferred. One more thing, the main goal of enquirer-compat is to provide the same API of Inquirer, however I am considering exporting a function that people can use the function to convert the questions array, while keeping the same API which behaves like Inquirer.

g-plane avatar Nov 13 '18 12:11 g-plane

Have just noticed another issue when moving from inquirer to enquirer: the skip / when option doesn't work as expected. In inquirer, you receive an object containing previous answers keyed by the question name. In enquirer, you receive the current questions' name, current questions answer, and no this context.

The values given to skip mean that you can't decide to skip a question based on previous answers.

ThisIsMissEm avatar Aug 18 '20 10:08 ThisIsMissEm

@ThisIsMissEm I thought I had seen some examples and tests showing how skip is used, but I haven't found what I'm looking for to be able to provide a link.

skip does receive a this context, and the current answers (when using the static Enquirer.prompt method), is found on this.state.answers.

You just need to ensure the skip function that you pass in is not using the "fat arrow" syntax, so it should look something like this:

const { prompt } = require('enquirer');
const questions = [
  { type: 'confirm', name: 'subscribe', message: 'Would you like to subscribe to our newsletter?' },
  {
    type: 'input',
    name: 'email',
    message: 'Email address',
    skip() {
      return this.state.answers.subscribe === false;
    }
  }
];

prompt(questions).then(console.log).catch(console.error);

After trying that, if you're still having a problem with .skip, feel free to open a new issue with an example of the code you're using.

doowb avatar Aug 18 '20 15:08 doowb

Yeah, this.state was undefined.

ThisIsMissEm avatar Aug 19 '20 10:08 ThisIsMissEm

I guess I may have been using arrow syntax without realising it. Would it be better as an API to just pass answers or state in via parameters to the function?

ThisIsMissEm avatar Aug 19 '20 10:08 ThisIsMissEm

Would it be better as an API to just pass answers or state in via parameters to the function?

Yes, you can do this, however the answers object is shallow cloned to avoid mutation.

Here is how it works. Instead of doing this:

const { prompt } = require('enquirer');

You'll need to instantiate Enquirer:

const Enquirer = require('enquirer');

// you can optionally pass an "answers" object as the second argument when instantiating
const options = {};
const answers = { foo: 'bar' };
const enquirer = new Enquirer(options, answers);

(async () => {

  await enquirer.prompt({
    name: 'username',
    type: 'input',
    message: 'What is your username?'
  });

  // however, the answers object you pass won't be updated. it's more useful
  // if you want to pre-populate values for skipping prompts, etc. 
  // You can get the updated answer values on `enquirer.answers`
  console.log('ANSWERS:', enquirer.answers);
  //=> ANSWERS: { foo: 'bar', username: 'jonschlinkert' }
})();

2020-08-19 19 22 06

You can also do this:

const Enquirer = require('enquirer');
const enquirer = new Enquirer();

// Listen for 'prompt' - this is emitted when a new prompt is 
// initialized, before the prompt is actually run
enquirer.on('prompt', prompt => {

  // listen for the prompt the be submitted by the user
  prompt.on('submit', value => {
    console.log({ [prompt.name]: value });
  });

  // you can also listen for "cancel"
  prompt.on('cancel', console.error);
});

enquirer.prompt({
  name: 'username',
  type: 'input',
  message: 'What is your username?'
});

Or this, if you want something more terse:

const { prompt } = require('enquirer');

prompt.on('prompt', p => p.on('submit', value => console.log({ [p.name]: value })));

prompt({
  name: 'username',
  type: 'input',
  message: 'What is your username?'
});

jonschlinkert avatar Aug 19 '20 23:08 jonschlinkert

@jonschlinkert how does this work with typescript? I'm guessing:

interface ConfigureAnswers {
  name: string;
  email: string;
  github: string;
}

const answers: Partial<ConfigureAnswers> = {};
const enquirer = new Enquirer<ConfigureAnswers>({}, answers);

(async () => {

  await enquirer.prompt({
    name: 'name',
    type: 'input',
    message: 'What is your username?'
  });

  // however, the answers object you pass won't be updated. it's more useful
  // if you want to pre-populate values for skipping prompts, etc. 
  // You can get the updated answer values on `enquirer.answers`
  console.log('ANSWERS:', enquirer.answers);
  //=> ANSWERS: { name: 'jonschlinkert' }
})();

Though, what will the type be of enquirer.answers?

ThisIsMissEm avatar Aug 20 '20 03:08 ThisIsMissEm

how does this work with typescript?

I don't use typescript. Sorry maybe someone else can help with that.

jonschlinkert avatar Aug 20 '20 23:08 jonschlinkert

@jonschlinkert i maintain a popular repo and am getting grief for not supporting TypeScript. Mind you, our library works just fine with TypeScript, just need to load it in CommonJS format, big deal!?! Props for this candid answer 🤓

mikeerickson avatar Aug 21 '20 00:08 mikeerickson