Skip to content

Commit

Permalink
Move inquirer code to console reporter (#3207)
Browse files Browse the repository at this point in the history
* Move inquirer code to console reporter

* Fix double ctrl+c to exit problem
  • Loading branch information
torifat authored and bestander committed Apr 22, 2017
1 parent ba45162 commit 504b8cf
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 45 deletions.
12 changes: 12 additions & 0 deletions __tests__/reporters/base-reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,15 @@ test('BaseReporter.termstrings', () => {
const expected = '"\u001b[2mjsprim#\u001b[22mjson-schema" not installed';
expect(reporter.lang('packageNotInstalled', '\u001b[2mjsprim#\u001b[22mjson-schema')).toEqual(expected);
});

test('BaseReporter.prompt', async () => {
const reporter = new BaseReporter();
let error;
try {
await reporter.prompt('', []);
} catch (e) {
error = e;
}
expect(error).not.toBeUndefined();
reporter.close();
});
81 changes: 36 additions & 45 deletions src/cli/commands/upgrade-interactive.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import {Add} from './add.js';
import {Install} from './install.js';
import Lockfile from '../../lockfile/wrapper.js';

const tty = require('tty');

export const requireLockfile = true;

export function setFlags(commander: Object) {
Expand All @@ -23,25 +21,6 @@ export function hasWrapper(): boolean {
return true;
}

type InquirerResponses<K, T> = {[key: K]: Array<T>};

// Prompt user with Inquirer
async function prompt(choices): Promise<Array<Dependency>> {
let pageSize;
if (process.stdout instanceof tty.WriteStream) {
pageSize = process.stdout.rows - 2;
}
const answers: InquirerResponses<'packages', Dependency> = await inquirer.prompt([{
name: 'packages',
type: 'checkbox',
message: 'Choose which packages to update.',
choices,
pageSize,
validate: (answer) => !!answer.length || 'You must choose at least one package.',
}]);
return answers.packages;
}

export async function run(
config: Config,
reporter: Reporter,
Expand Down Expand Up @@ -109,31 +88,43 @@ export async function run(
(ys, y) => ys.concat(Array.isArray(y) ? flatten(y) : y), [],
);

const choices = Object.keys(groupedDeps).map((key) => [
const choices = flatten(Object.keys(groupedDeps).map((key) => [
new inquirer.Separator(reporter.format.bold.underline.green(key)),
groupedDeps[key],
new inquirer.Separator(' '),
]);

const answers = await prompt(flatten(choices));

const getName = ({name}) => name;
const isHint = (x) => ({hint}) => hint === x;

await [null, 'dev', 'optional', 'peer'].reduce(async (promise, hint) => {
// Wait for previous promise to resolve
await promise;
// Reset dependency flags
flags.dev = hint === 'dev';
flags.peer = hint === 'peer';
flags.optional = hint === 'optional';

const deps = answers.filter(isHint(hint)).map(getName);
if (deps.length) {
reporter.info(reporter.lang('updateInstalling', getNameFromHint(hint)));
const add = new Add(deps, flags, config, reporter, lockfile);
return await add.init();
}
return Promise.resolve();
}, Promise.resolve());
]));

try {
const answers: Array<Dependency> = await reporter.prompt(
'Choose which packages to update.',
choices,
{
name: 'packages',
type: 'checkbox',
validate: (answer) => !!answer.length || 'You must choose at least one package.',
},
);

const getName = ({name}) => name;
const isHint = (x) => ({hint}) => hint === x;

await [null, 'dev', 'optional', 'peer'].reduce(async (promise, hint) => {
// Wait for previous promise to resolve
await promise;
// Reset dependency flags
flags.dev = hint === 'dev';
flags.peer = hint === 'peer';
flags.optional = hint === 'optional';

const deps = answers.filter(isHint(hint)).map(getName);
if (deps.length) {
reporter.info(reporter.lang('updateInstalling', getNameFromHint(hint)));
const add = new Add(deps, flags, config, reporter, lockfile);
return await add.init();
}
return Promise.resolve();
}, Promise.resolve());
} catch (e) {
Promise.reject(e);
}
}
8 changes: 8 additions & 0 deletions src/reporters/base-reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
Package,
ReporterSpinner,
QuestionOptions,
PromptOptions,
} from './types.js';
import type {LanguageKeys} from './lang/en.js';
import type {Formatter} from './format.js';
Expand Down Expand Up @@ -259,4 +260,11 @@ export default class BaseReporter {
disableProgress() {
this.noProgress = true;
}

//
prompt<T>(
message: string, choices: Array<*>, options?: PromptOptions = {},
): Promise<Array<T>> {
return Promise.reject(new Error('Not implemented'));
}
}
51 changes: 51 additions & 0 deletions src/reporters/console/console-reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
ReporterSpinner,
ReporterSelectOption,
QuestionOptions,
PromptOptions,
} from '../types.js';
import type {FormatKeys} from '../format.js';
import BaseReporter from '../base-reporter.js';
Expand All @@ -15,13 +16,16 @@ import Spinner from './spinner-progress.js';
import {clearLine} from './util.js';
import {removeSuffix} from '../../util/misc.js';
import {sortTrees, recurseTree, getFormattedOutput} from './helpers/tree-helper.js';
import inquirer from 'inquirer';

const {inspect} = require('util');
const readline = require('readline');
const chalk = require('chalk');
const read = require('read');
const tty = require('tty');

type Row = Array<string>;
type InquirerResponses<K, T> = {[key: K]: Array<T>};

export default class ConsoleReporter extends BaseReporter {
constructor(opts: Object) {
Expand Down Expand Up @@ -380,4 +384,51 @@ export default class ConsoleReporter extends BaseReporter {
bar.tick();
};
}

async prompt<T>(
message: string, choices: Array<*>, options?: PromptOptions = {},
): Promise<Array<T>> {
if (!process.stdout.isTTY) {
return Promise.reject(new Error("Can't answer a question unless a user TTY"));
}

let pageSize;
if (process.stdout instanceof tty.WriteStream) {
pageSize = process.stdout.rows - 2;
}

const rl = readline.createInterface({
input: this.stdin,
output: this.stdout,
terminal: true,
});

// $FlowFixMe: Need to update the type of Inquirer
const prompt = inquirer.createPromptModule({
input: this.stdin,
output: this.stdout,
});

let rejectRef = () => {};
const killListener = () => {
rejectRef();
};

const handleKillFromInquirer = new Promise((resolve, reject) => {
rejectRef = reject;
});

rl.addListener('SIGINT', killListener);

const {name = 'prompt', type = 'input', validate} = options;
const answers: InquirerResponses<string, T> = await Promise.race([
prompt([{name, type, message, choices, pageSize, validate}]),
handleKillFromInquirer,
]);

rl.removeListener('SIGINT', killListener);
rl.close();

return answers[name];
}
}
7 changes: 7 additions & 0 deletions src/reporters/noop-reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
Package,
ReporterSpinner,
QuestionOptions,
PromptOptions,
} from './types.js';
import type {LanguageKeys} from './lang/en.js';
import type {Formatter} from './format.js';
Expand Down Expand Up @@ -79,4 +80,10 @@ export default class NoopReporter extends BaseReporter {
disableProgress() {
this.noProgress = true;
}

prompt<T>(
message: string, choices: Array<*>, options?: PromptOptions = {},
): Promise<Array<T>> {
return Promise.reject(new Error('Not implemented'));
}
}
9 changes: 9 additions & 0 deletions src/reporters/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,12 @@ export type QuestionOptions = {
password?: boolean,
required?: boolean,
};

export type InquirerPromptTypes = 'list' | 'rawlist' | 'expand' |
'checkbox' | 'confirm' | 'input' | 'password' | 'editor';

export type PromptOptions = {
name?: string,
type?: InquirerPromptTypes,
validate?: (input: string | Array<string>) => (boolean | string),
};

0 comments on commit 504b8cf

Please sign in to comment.