src/app/prompter.js
import camelCase from 'lodash.camelcase';
import kebabCase from 'lodash.kebabcase';
import chalk from 'chalk';
import { v4 as uuid } from 'uuid';
import Github from './lib/github';
import { login as npmLogin } from './lib/npm';
import options from './options';
import prompts from './prompts';
export default class Prompter {
constructor(generator) {
this.generator = generator;
this._setGeneratorOptions();
}
/**
* Main prompter method
*/
async prompt() {
this._initProps();
await this._promptGeneral();
await this._promptEsdoc();
await this._promptGithub();
await this._promptTravis();
await this._promptCoveralls();
await this._promptNpm();
await this._promptSemanticRelease();
await this._promptPasswords();
return this.props;
}
/*
Sub prompters
*/
async _promptGeneral() {
const answers = await this.generator.prompt([
this._buildPrompt('projectName', {
...prompts.projectName,
default: kebabCase(this.generator.appname),
filter: kebabCase,
}),
this._buildPrompt('description', prompts.description),
this._buildPrompt('name', {
...prompts.name,
default: this.generator.user.git.name(),
}),
this._buildPrompt('email', {
...prompts.email,
default: this.generator.user.git.email(),
}),
this._buildPrompt('website', prompts.website),
]);
this._updatePropsWithAnswers(answers);
this.props.camelProject = camelCase(this.props.projectName);
}
async _promptEsdoc() {
const { esdoc } = await this.generator.prompt([
this._buildPrompt('esdoc', prompts.esdoc),
]);
this._updatePropsWithAnswers({ esdoc: esdoc || false });
}
async _promptGithub() {
const answers = await this.generator.prompt([
this._buildPrompt('githubUsername', prompts.githubUsername),
this._buildPrompt('githubTemplates', prompts.githubTemplates),
this._buildPrompt(
'createGithubRepository',
prompts.createGithubRepository
),
]);
this._updatePropsWithAnswers(answers);
this.props.repository = `${this.props.githubUsername}/${this.props.projectName}`;
}
async _promptTravis() {
const { travisCI } = await this.generator.prompt([
this._buildPrompt('travisCI', prompts.travisCI),
]);
this._updatePropsWithAnswers({ travisCI });
}
async _promptCoveralls() {
const { coveralls } = await this.generator.prompt([
this._buildPrompt('coveralls', prompts.coveralls),
]);
this._updatePropsWithAnswers({ coveralls: coveralls || false });
}
async _promptNpm() {
const { npmDeploy } = await this.generator.prompt([
this._buildPrompt('npmDeploy', prompts.npmDeploy),
]);
this._updatePropsWithAnswers({
npmDeploy: npmDeploy || false,
});
}
async _promptSemanticRelease() {
const { semanticRelease } = await this.generator.prompt([
this._buildPrompt('semanticRelease', prompts.semanticRelease),
]);
this._updatePropsWithAnswers({
semanticRelease: semanticRelease || false,
});
}
async _promptPasswords() {
const {
createGithubRepository,
npmDeploy,
githubToken,
npmToken,
travisCI,
} = this.props;
if (createGithubRepository || travisCI) {
if (!githubToken) {
this._logGithubPasswordRequired();
this._logPasswordSafety();
}
await this._loginToGithub();
}
if (!npmToken && npmDeploy) {
this._logNpmPasswordRequired();
this._logPasswordSafety();
await this._loginToNpm();
}
}
/*
Login prompters
*/
async _loginToNpm() {
const { npmUsername, npmPassword } = await this._promptNpmLogin();
// generate npm-token
const { token: npmToken } = await npmLogin({
username: npmUsername,
password: npmPassword,
});
this.props = { ...this.props, npmToken };
}
async _loginToGithub() {
const {
githubToken: token,
githubUsername: username,
semanticRelease,
repository,
} = this.props;
if (token) {
this.props.github = new Github({ token });
await this.props.github.authenticate();
return;
}
const { githubPassword: password } = await this._promptGithubPassword();
const note = `${repository}/travis-${uuid().slice(-4)}`;
const noteUrl = 'https://github.com/sharvit/generator-node-mdl';
const scopes = semanticRelease
? [
'repo',
'read:org',
'user:email',
'repo_deployment',
'repo:status',
'write:repo_hook',
]
: [];
this.props.github = new Github({
username,
password,
note,
noteUrl,
scopes,
on2Fa: (...args) => this.github2fa(...args),
});
this.props.githubToken = await this.props.github.authenticate();
}
_promptNpmLogin() {
return this.generator.prompt([
{
name: 'npmUsername',
message: 'Your npm username:',
store: true,
},
{
name: 'npmPassword',
message: 'Your npm password:',
type: 'password',
},
]);
}
_promptGithubPassword() {
const { githubUsername } = this.props;
return this.generator.prompt([
{
name: 'githubPassword',
message: `Enter you github password for ${githubUsername}:`,
type: 'password',
},
]);
}
_promptGithub2fa() {
return this.generator.prompt([
{
name: 'github2fa',
message: `Enter your github two-factor authentication Code:`,
type: 'password',
},
]);
}
async github2fa() {
const { github2fa } = await this._promptGithub2fa();
return github2fa;
}
/*
Loggers
*/
_logGithubPasswordRequired() {
const { createGithubRepository } = this.props;
if (createGithubRepository) {
this.generator.log(
'\nYour github password is required so I can create a github repository for you.'
);
} else {
this.generator.log(
'\nYour github password is required so I can install semantic-release for you.'
);
}
}
_logNpmPasswordRequired() {
this.generator.log(
'\nYour npm username and password is required so I can install an automated npm-deploy for you.'
);
}
_logPasswordSafety() {
this.generator.log(
`${chalk.bold('I will never store your passwords')} 🙌 🙌 🙌 \n`
);
}
/*
Helpers
*/
_setGeneratorOptions() {
for (const [name, { type, desc }] of Object.entries(options)) {
this.generator.option(name, { type, desc });
}
}
_initProps() {
this.props = {};
for (const optionName of Object.keys(options)) {
const { [optionName]: option } = this.generator.options;
if (option) {
this.props[optionName] = option;
}
}
}
_updatePropsWithAnswers(answers) {
for (const [name, value] of Object.entries(answers)) {
if (value) {
this.props[name] = value;
}
}
}
_buildPrompt(name, prompt) {
const result = { name, message: prompt.desc };
if (prompt.default) result.default = prompt.default;
if (prompt.store) result.store = prompt.store;
if (prompt.filter) result.filter = prompt.filter;
switch (prompt.type) {
case Boolean:
result.type = 'confirm';
result.default = this.props.noDefaults ? false : result.default;
break;
case String:
default:
result.type = 'input';
break;
}
result.when = () => {
if (this.props[name] !== undefined) return false;
if (this.props.noDefaults && prompt.type === Boolean) return false;
if (prompts[name].dependOn) {
for (const dependOn of prompts[name].dependOn) {
if (!this.props[dependOn]) {
return false;
}
}
}
prompts[name].help && this.generator.log(prompts[name].help());
return true;
};
if (prompt.required) {
result.validate = (input) => {
if (
input === undefined ||
(typeof input === 'string' &&
input.trim() === '' &&
prompt.default === undefined)
) {
return `${name} is required`;
}
return true;
};
}
return result;
}
}