Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move inquirer code to console reporter #3207

Merged
merged 2 commits into from
Apr 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = () => {};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't like doing this at all. Please feel free to suggest any better alternative.

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),
};