How to pass parameters to a inquirer question? - javascript

How to pass parameters to the inquirer questions, so that i can set the values of a question object based on either values from previous questions or from code outside the prompt?
The only way i can see of achieving this if based on answer of a previous question is to nest the inquirer prompt calls
const inquirer = require('inquirer');
function getPath(){
return {
'system1':`system1path`,
'system2':`system2path`,
'system3':`system3path`
}
}
inquirer.prompt([
{
type: 'list',
name: 'testSystem',
message: 'Which system do you want to run?',
choices: ['system1', 'system2', 'system3']
},
{
type: 'fuzzypath',
name: 'searchTestSuite',
excludePath: nodePath => nodePath.startsWith('node_modules'),
itemType: 'any',
rootPath: getPath()['< answer from question(testSystem) >'],
message: 'Select a target directory :',
default: `system1path`,
suggestOnly: false,
depthLimit: 6,
},
]).then(answers => {
console.log(answers);
});
Expected result :
If you select testSystem = system2
You should get rootPath = system2Path , without nesting the inquirer prompts or by using whenfunction (since when seems to be dealing with boolean values)

You can solve it by nesting, but changing from Promise.then to async-await makes it more readable. Inquirer.js uses promises, hence you can use await to capture prompt answers and by issuing multiple prompts you can save the state between prompts. See code below.
PS: I've removed the default: ..., parameter from fuzzypath because it yields the default value despite it beign outside the root path.
const inquirer = require('inquirer');
inquirer.registerPrompt('fuzzypath', require('inquirer-fuzzy-path'))
const system1path = ...;
const system2path = ...;
const system3path = ...;
function getPath(){
return {
'system1': `${system1path}`,
'system2': `${system2path}`,
'system3': `${system3path}`
};
}
(async function () {
const {testSystem} = await inquirer.prompt({
type: 'list',
name: 'testSystem',
message: 'Which system do you want to run?',
choices: ['system1', 'system2', 'system3']
});
const {searchTestSuite} = await inquirer.prompt({
type: 'fuzzypath',
name: 'searchTestSuite',
excludePath: nodePath => nodePath.startsWith('node_modules'),
itemType: 'any',
rootPath: getPath()[testSystem],
message: 'Select a target directory :',
suggestOnly: false,
depthLimit: 6,
});
console.log({testSystem, searchTestSuite});
})();

Related

Repetitive prompting with validation in Inquirer.js when using a validation function featuring fetch

In the context of Inquirer.js, one of my question-prompts involves a validation function to check if the user-supplied Github username exists. This validation function uses an fetch function in order to check if the Github username exits.
The problem is that Inquirier repeats the prompt 3 times. Once with a ? prompt, again with a ⠹ prompt, and a third time with a ? prompt, followed by the custom error message returned which lets the user know that the username did not appear to exist.
Screenshot here
This repetitive occurrence of the prompt is very confusing to the end user, and I want to know how to limit the prompt to displaying once only. I don't really understand this confusing repetitive prompt behavior to begin with.
// Include packages needed for this application
// const fs = require('fs');
import * as fs from "fs";
// const fetch = require('node-fetch');
import fetch from "node-fetch";
// const inquirer = require('inquirer');
import inquirer from "inquirer";
const validateUsername = async function (input) {
console.log(input);
const response = await fetch(
`https://api.github.com/users/${input}`
);
// console.log(response);
const data = await response.json();
console.log(data.message);
if (data.message) {
if (data.message === "Not Found") {
return "User was not found, please use a valid username.";
}
} else {
return true;
}
};
inquirer
.prompt([
{
name: "projectName",
message: "What the project name?",
type: "input",
},
{
name: "projectDescription",
message: "What the project description?",
type: "input",
},
{
type: "input",
name: "githubUser",
message:
"What is your Github username? (This is needed for Shields badges and is used to retrieve code size, repo size, repo file count and licence type)",
validate: validateUsername,
},
{
name: "githubRepoName",
message:
"What is the Github repository name? (This is needed for Shields badges and is used to retrieve code size, repo size, repo file count)",
type: "input",
},
{
name: "contribution",
message:
"Please provide your call to contributions and any relevant information here.",
type: "input",
},
{
type: "input",
name: "licence",
message: "What type of licence applies to your project?",
},
{
type: "input",
name: "twitter",
message: "Twitter handle? Enter without input to skip.",
},
{
type: "input",
name: "twitter",
message: "Facebook handle? Enter without input to skip.",
},
])
.then((answers) => {
// do some stuff with answers
}
I haven't tried this myself, but one idea would be to isolate the question that has async validation, and make you're own validator that calls inquirer, validates and calls again...
// prompt user for gh name, check and retry if user name is invalid
async function promptAndValidateGHUser(message) {
const answer = await inquirer.prompt([{ type: 'input', name: 'ghuser', message }])
const response = await fetch(
`https://api.github.com/users/${answer.ghuser}`
);
// console.log(response);
const data = await response.json();
console.log(data.message);
if (data.message) {
if (data.message === "Not Found") {
return promptAndValidateGHUser("User was not found, please use a valid username.")
}
} else {
return answer;
}
}
The way to call is to build a separate promise each for the questions before the GitHub question, the GitHub question itself, and the question after.
const listA = [
{
name: "projectName",
message: "What the project name?",
type: "input",
},
// ...
// up to, but not including the github question
]
const ghMessage = "What is your Github username? (This is needed for Shields badges and is used to retrieve code size, repo size, repo file count and licence type)",
const listB = [
// the rest of the qustions
{
name: "contribution",
message:
"Please provide your call to contributions and any relevant information here.",
type: "input",
},
// ...
]
Then call them in a row, like this:
async function prompts() {
const answersA = await inquirer.prompt(listA)
const gHubAnswer = await promptAndValidateGHUser(ghMessage)
const answersB = await inquirer.prompt(listB)
}

How can I use more than one inquirer at once for JavaScript

inquirer
.prompt([
{
name: 'name',
message: 'Enter team members name.',
},
{
name: 'role',
type: 'list',
message: 'Enter a team members role',
choices: [
'Engineer',
'Manager',
'Intern'
],
},
{
name: 'id',
message: 'Please enter team members id',
type: 'input'
},
{
name: 'email',
message: 'Please enter team members email'
}
])
.then(function(data){
let moreRole = '';
if (data.role === 'Engineer') {
moreRole = 'Github username.'
}else if(data.role === 'Intern') {
moreRole = 'school name.'
}else {
moreRole = 'office Number.'
}
})
inquirer
.prompt([
{
message: `Enter team members ${moreRole}`,
name: 'moreRole'
},
{
type: 'list',
message: 'Would you like to add another team member?',
choices: [
'Yes',
'No'
],
name: 'anotherMember'
}
])
.then(moredata => {
console.log(moredata)
});
So the first inquirer works but when I add the second one and run it. It doesn't work properly. Can I not use two inquirers back to back? How would I get it to work. In the console it just asks me the first question and and goes back out and doesn't let me answer it.
The API of the package is based on Promises. The first inquirer.prompt call fires the first prompt, but since you are using the then way of handling resolved Promises, it is not waiting for the operation to complete, it just goes through to the second inquirer.prompt call.
To bypass this, you could wrap your code into an async function and prepend await to each inquirer.prompt (or at least to all but the last one).
Should look something like this:
async function callInquirers() {
const inq1 = await inquirer.prompt([...]);
const inq2 = await inquirer.prompt([...]);
// do stuff with results inq1 and inq2
}
Check docs for Promises, event loop, async/await if you want more insight on this matter.

Discord.js. Cannot add permissions to slash commands

Im trying to add permission to commands in such way, that only users with specific role can use it. At first im creating commands like its said in documentation.
Then Im trying to get each command and adding to them new permissions:
await commands.forEach(command => {
const permissions2 = [
{
id: guild.roles.everyone.id,
type: 'ROLE',
permission: false,
}
];
const permissions1 = [
{
id: botRole.id,
type: 'ROLE',
permission: true,
},
];
console.log(`Changing command ${command.id}`);
command.permissions.add(permissions2);
command.permissions.add(permissions1);
});
But whatever I do I get this error: TypeError [INVALID_TYPE]: Supplied permissions is not an Array of ApplicationCommandPermissionData.
Ive also tried running this code as shown in documentation but got same result:
await commands.forEach(command => {
...
console.log(`Changing command ${command.id}`);
command.permissions.add({permissions2});
command.permissions.add({permissions1});
});
Changing code to this helped:
const permissions2 = {
id: guild.roles.everyone.id,
type: 'ROLE',
permission: false,
};
const permissions1 = {
id: botRole.id,
type: 'ROLE',
permission: true,
};
let commandsList = await guild.commands.fetch();
await commandsList.forEach(slashCommand => {
console.log(`Changing command ${slashCommand.id}`);
//set the permissions for each slashCommand
guild.commands.permissions.add({
command: slashCommand.id,
permissions: [permissions1, permissions2]
});
});
I dont think you can use the forEach loop like you did.
commands.permissions#set consists of an object with the id of the command you want to edit, and an array with the permissions.
So you would have to rewrite your code to this:
//create the permissions objects
const permissions2 = {
id: guild.roles.everyone.id,
type: 'ROLE',
permission: false,
};
const permissions1 = {
id: botRole.id,
type: 'ROLE',
permission: true,
};
//loop through all the slashCommands
await commands.forEach(slashCommand => {
console.log(`Changing command ${slashCommand.id}`);
//set the permissions for each slashCommand
client.application.commands.permissions.set({command: slashCommand.id, permissions: [permissions1, permissions2]});
});
Read more about setting permissions for slashCommands here

Mongoose Schema.index on multiple fields does not work with tests

I have created an uploadSchema (mongoose.Schema) with the fields (among the rest): key and bucket. each of them alone is not unique but together I want them to create a unique id.
in my code, I used the line (right after declaring the uploadSchema and right before the uploadModel):
uploadSchema.index({ key: 1, bucket: 1 }, { unique: true, background: true });
but then, in my tests (mocha and chai), the indexing is not enforced, and so I can create two instances with the same key and bucket (in my case).
for example, in my code:
await uploadModel.create({ key: testUpload.key, bucket: testUpload.bucket,
name: 'name1', ownerID: USER.id, parent: null }).should.eventually.exist;
and right after that:
await uploadModel.create({key: testUpload.key, bucket: testUpload.bucket,
name: 'name1', ownerID: USER.id, parent: null }).should.eventually.be.rejected;
does not throw the right error error:
AssertionError: expected promise to be rejected but it was fulfilled with { Object ($__, isNew, ...) }
Am I not using it correctly? Or is there a problem with indexing and testing?
Most likely you set autoIndex to false in your connection (which is recommended to do).
Either add it to you Schema:
let uploadSchema = mongoose.Schema({ ... }, {autoIndex: true});
But i would recommend just building the index yourself on the database, i think its the safest way around it.
so I figured it out!
Apparently, I used mongoose.connection.dropDatabase(); in my afterEach of the tests. That means that the indexes were reset each time.
So what I did was to recreate the indexes each time in my tests:
before(async () => {
// Remove files from DB
const collections = ['files', 'uploads'];
for (const i in collections) {
mongoose.connection.db.createCollection(collections[i], (err) => {});
}
await mongoose.connection.collections['files'].createIndex({ name: 1, parent: 1, ownerID: 1 }, { unique: true });
await mongoose.connection.collections['uploads'].createIndex({ key: 1, bucket: 1 }, { unique: true });
});
And in the beforeEach:
beforeEach(async () => {
const removeCollectionPromises = [];
for (const i in mongoose.connection.collections) {
removeCollectionPromises.push(mongoose.connection.collections[i].deleteMany({}));
}
await Promise.all(removeCollectionPromises);
});
afterEach is empty.
now it works :)

How to do a recursive prompt in Yeoman with promises?

I'm trying to figure out how to do a recursive prompt with a yeoman generator using promises. I am trying to produce a form generator that will first ask for a name for the form component and then ask for a name (that will be used as an id) for each input (ie: firstName, lastName, username, etc.). I've found answers for this question using callbacks but I would like to stick with promises. Below is the code I have so far and what I am attempting to do for the recursion but is not working. Any help and advice is appreciated thank you in advance!
const Generator = require('yeoman-generator')
const questions = [
{ type: 'input',
name: 'name',
message: 'What is the name of this form?',
default: 'someForm'
},
{
type: 'input',
name: 'input',
message: 'What is the name of the input?'
},
{
type: 'confirm',
name: 'askAgain',
message: 'Is there another input to add?'
}
]
module.exports = class extends Generator {
prompting() {
return this.prompt(questions).then((answers) => {
if (answers.askAgain) {
this.prompting()
}
this.userOptions = answers
this.log(this.userOptions)
})
}
}
For anyone that stumbles across this post looking for an answer this is what I ended up doing to make it work. As you can see in my Form class that extends Generator I have a method called prompting() in there. This is a method recognized by Yeoman's loop as a priority and it will not leave this method until something is returned. Since I'm returning a promise it will wait until my promise is finished before moving on. For my first prompt that's exactly what I need but for the second one to happen in prompting2 you can add
const done = this.async()
at the start of your method. This tells yeoman that you are going to have some asynchronous code happen and not to move past the method containing this until done is executed. If you do not use this and have another priority method in your class after it, such as writing() for when you are ready to produce your generated code, then yeoman will move past your method without waiting for your asynchronous code to finish. And you can see in my method prompting2() that I recursively call it whenever the user states that there is another input to name and it will continue doing so until they say there is not another input to name. I'm sure there is a better way to do this but it is working great for me this way. I hope this helps anyone that is looking for a way to do this!
const Generator = require('yeoman-generator')
const questions = [
{ type: 'input',
name: 'name',
message: 'What is the name of this form?',
default: 'someForm'
}
]
const names = [
{
type: 'input',
name: 'inputs',
message: 'What is the name of the input?',
default: '.input'
},
{
type: 'confirm',
name: 'another',
message: "Is there another input?",
default: true
}
]
const inputs = []
class Form extends Generator {
prompting() {
return this.prompt(questions).then((answers) => {
this.formName = answers.name
this.log(this.formName)
})
}
prompting2 () {
const done = this.async()
return this.prompt(names).then((answers) => {
inputs.push(answers.inputs)
if (answers.another) this.prompting2()
else {
this.inputs = inputs
this.log(this.inputs)
done()
}
})
}
}
module.exports = Form

Categories

Resources