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

custom exit handlers #405

Closed
aydn opened this issue Aug 8, 2016 · 7 comments
Closed

custom exit handlers #405

aydn opened this issue Aug 8, 2016 · 7 comments

Comments

@aydn
Copy link

aydn commented Aug 8, 2016

https://github.com/SBoudrias/Inquirer.js/blob/master/lib/ui/baseUI.js#L21

this.rl.on('SIGINT', this.onForceClose);
process.on('exit', this.onForceClose);

Please provide an option or function to set exit handlers our own.

@SBoudrias
Copy link
Owner

Why not listen to those events yourself?

@aydn
Copy link
Author

aydn commented Aug 9, 2016

Those events never triggers in prompt mode. I want to return back to prev. menu or cancel a step with this keys.

'use strict';

process.on('SIGINT', () => console.log('bye!'));

const inquirer = require('inquirer');

inquirer.prompt({
  type: 'input',
  name: 'test',
  message: 'hit CTRL + C to test SIGINT in prompt mode'
});

@cqcwillard
Copy link

You can pass your own event listener like @SBoudrias suggested. I ended up doing this in my app since I needed synchronous behavior.

let stdin = process.stdin;
stdin.on("data", (key) => {
    if (key == "\u0003") {
        LOG("catching ctrl+c");
    }
});

inquirer.prompt(questions, {input: stdin});

@justjake
Copy link

I would prefer prompt() to return a rejected promise on Ctrl-C. This is achievable if you're willing to reach in and monkey with a Prompt object's readline directly:

/**
 * By default Inquirer handles Ctrl-C itself by force-quitting the process with
 * no way to clean up. This wrapper around Inquirer throws a Error
 * instead, allowing normal exception handling.
 */
async function safePrompt<T>(question: inquirer.Question<T>): Promise<T> {
	const promptModule = inquirer.createPromptModule()
	const ui = new inquirer.ui.Prompt((promptModule as any).prompts, {})
	const deferred = PromiseUtils.deferred<T>()

	// Remove the force-quit behavior
	const rl = ui.rl
	rl.listeners("SIGINT").forEach(listener => rl.off("SIGINT", listener as any))

	// Insert our listener to reject the promise
	function handleCtrlC() {
		// remove the listener
		rl.off("SIGINT", handleCtrlC)

		// Clean up inquirer
		ui.close()

		// Then reject our promise
		deferred.reject(
			new Error("Aborted due to Ctrl-C during a prompt", ui)
		)
	}
	rl.on("SIGINT", handleCtrlC)

	// Run the UI
	ui.run<T>([question]).then(deferred.resolve, deferred.reject)
	return await deferred.promise
}

(Implementation of PromiseUtils.deferred left as an exercise to the reader)

@vorpax
Copy link

vorpax commented Nov 28, 2020

I would prefer prompt() to return a rejected promise on Ctrl-C. This is achievable if you're willing to reach in and monkey with a Prompt object's readline directly:

/**
 * By default Inquirer handles Ctrl-C itself by force-quitting the process with
 * no way to clean up. This wrapper around Inquirer throws a Error
 * instead, allowing normal exception handling.
 */
async function safePrompt<T>(question: inquirer.Question<T>): Promise<T> {
	const promptModule = inquirer.createPromptModule()
	const ui = new inquirer.ui.Prompt((promptModule as any).prompts, {})
	const deferred = PromiseUtils.deferred<T>()

	// Remove the force-quit behavior
	const rl = ui.rl
	rl.listeners("SIGINT").forEach(listener => rl.off("SIGINT", listener as any))

	// Insert our listener to reject the promise
	function handleCtrlC() {
		// remove the listener
		rl.off("SIGINT", handleCtrlC)

		// Clean up inquirer
		ui.close()

		// Then reject our promise
		deferred.reject(
			new Error("Aborted due to Ctrl-C during a prompt", ui)
		)
	}
	rl.on("SIGINT", handleCtrlC)

	// Run the UI
	ui.run<T>([question]).then(deferred.resolve, deferred.reject)
	return await deferred.promise
}

(Implementation of PromiseUtils.deferred left as an exercise to the reader)

Hey, i'm using inquirer on nodejs, i would like to use this function but in a js file

@phaethon5882
Copy link

i called the safePrompt using @justjake's code,

type Answer = {
  templateName: string;
};

export async function askTemplateName() {
  return safePrompt<Answer>([
    {
      name: 'templateName',
      type: 'list',
      message: 'template name:',
      choices: ['node.js', 'next.js'],
    },
  ]);
}

but i've seen an error like below,
i think i definitely passed the name property to the Question object.
i don't know why this error occurred.

node_modules/inquirer/lib/prompts/base.js:81
    throw new Error('You must provide a `' + name + '` parameter');
    at InputPrompt.throwParamError (node_modules/inquirer/lib/prompts/base.js:81:11)
    at new Prompt (node_modules/inquirer/lib/prompts/base.js:38:12)
    at new InputPrompt (node_modules/inquirer/lib/prompts/input.js:11:1)
    at PromptUI.fetchAnswer (node_modules/inquirer/lib/ui/prompt.js:106:25)
    at doInnerSub (node_modules/rxjs/src/internal/operators/mergeInternals.ts:71:15)
    at outerNext (node_modules/rxjs/src/internal/operators/mergeInternals.ts:53:58)
    at OperatorSubscriber._this._next (node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts:70:13)
    at OperatorSubscriber.Subscriber.next (node_modules/rxjs/src/internal/Subscriber.ts:75:12)
    at node_modules/rxjs/src/internal/operators/mergeInternals.ts:85:24
    at OperatorSubscriber._this._next (node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts:70:13)
  1. i implemented 'deferred promise' with reference to link,
export default class Deferred<T> {
  public promise: Promise<T>;

  // @ts-ignore
  public resolve: (value: T | PromiseLike<T>) => void;

  // @ts-ignore
  public reject: (reason?: any) => void;

  constructor() {
    this.promise = new Promise<T>((resolve, reject) => {
      this.resolve = resolve;
      this.reject = reject;
    });
  }
}
  1. and implement safePrompt like below.
export async function safePrompt<T>(question: DistinctQuestion<T>): Promise<T> {
  const promptModule = inquirer.createPromptModule();
  const ui = new inquirer.ui.Prompt<T>((promptModule as any).prompts);
  const deferred = new Deferred<T>();

  // Remove the force-quit behavior
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  const { rl } = ui;
  rl.listeners('SIGINT').forEach(listener => rl.off('SIGINT', listener as any));

  // Insert our listener to reject the promise
  function handleCtrlC() {
    // remove the listener
    rl.off('SIGINT', handleCtrlC);

    // Clean up inquirer
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    ui.close();

    // Then reject our promise
    deferred.reject(new Error(`Aborted due to Ctrl-C during a prompt. \n${ui.toString()}`));
  }
  rl.on('SIGINT', handleCtrlC);

  // Run the UI
  ui.run([question]).then(deferred.resolve, deferred.reject);
  return deferred.promise;
}

How can i catch the SIGINT signal?

@SBoudrias
Copy link
Owner

Closing this ticket as stale. Open new issues if you encounter problem with the new Inquirer API.

The new API expose a documented cancel method to stop a readline. And I think the SIGINT handling is cleaner; though lemme know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants