npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

prask

v2.2.0

Published

Lightweight prompting library for terminal apps.

Downloads

53

Readme

Prask

Lightweight prompting library for terminal apps.

Install

npm install --save prask

Prompts

| Input | Selection | Others | | ----------------------- | --------------------------- | ------------------- | | string | multiselect | log | | invisible | select | prompt | | password | boolean | spinner | | number | toggle | | | | rating | |

string

This prompt can be used to ask the user for a string value.

import {string} from 'prask';
import color from 'tiny-colors';

const result = await string ({
  /* REQUIRED OPTIONS */
  message: 'What is your name?', // The message that the user will read
  /* OPTIONAL OPTIONS */
  initial: 'John Doe', // Optional default value, to allow the user to quickly press Enter for it
  required: true, // Whether a non-empty value for this prompt is required or not
  format: ( value, settled ) => value.length >= 2 ? colors.green ( value ) : colors.red ( value ), // Optional formatting function, the visual length of the output string must remain the same
  validate: value => value.length >= 2 // Optional validation function
});

Interactions:

| Trigger | Description | | ------- | -------------------------------------------------------------------------------- | | Esc | Quit the prompt, which will resolve to undefined. | | Enter | Submit the value, or the initial value if visible, checking if it's valid first. | | Tab | Edit the initial value, if visible. |

invisible

This prompt can be used to ask the user for a password, without visually leaking even the length of the password to the console.

import {invisible} from 'prask';

const result = await invisible ({
  /* REQUIRED OPTIONS */
  message: 'What is your password?', // The message that the user will read
  /* OPTIONAL OPTIONS */
  initial: 'P@assword!', // Optional default value, to allow the user to quickly press Enter for it, better not to use it since it will be visible
  required: true, // Whether a non-empty value for this prompt is required or not
  validate: value => value.length >= 8 // Optional validation function
});

It can be interacted with exactly like a string prompt, the only difference is that the value is invisible.

password

This prompt can be used to ask the user for a password, without visually leaking the characters that make it up to the console.

import {password} from 'prask';

const result = await password ({
  /* REQUIRED OPTIONS */
  message: 'What is your password?', // The message that the user will read
  /* OPTIONAL OPTIONS */
  initial: 'P@assword!', // Optional default value, to allow the user to quickly press Enter for it, better not to use it since it will be visible
  required: true, // Whether a non-empty value for this prompt is required or not
  validate: value => value.length >= 8 // Optional validation function
});

It can be interacted with exactly like a string prompt, the only difference is that every character in the value will be replaced with an asterisk.

number

This prompt can be used to ask the user for a numeric value.

import {number} from 'prask';
import colors from 'tiny-colors';

const result = await number ({
  /* REQUIRED OPTIONS */
  message: 'What is your favorite even number?', // The message that the user will read
  /* OPTIONAL OPTIONS */
  initial: 42, // Optional default value, to allow the user to quickly press Enter for it
  format: ( value, settled ) => ( value % 2 ) ? colors.red ( value ) : colors.green ( value ), // Optional formatting function, the visual length of the output string must remain the same
  validate: value => ( value % 2 ) ? 'The number must be even' : true // Optional validation function
});

It can be interacted with exactly like a string prompt, the main difference is that the value of the input is also automatically validated to be a number, plus the following extra interactions are supported:

| Trigger | Description | | ------------- | --------------------------------- | | Up/Right | Increment the current value by 1. | | Down/Left | Decrement the current value by 1. |

multiselect

This prompt can be used to ask the user to pick from zero to many options between the provided ones.

import {multiselect} from 'prask';

const result = await multiselect ({
  /* REQUIRED OPTIONS */
  message: 'Which countries would you like to visit?', // The message that the user will read
  options: [
    { title: 'Italy', value: 'it' },
    { title: 'Turkey', value: 'tr' },
    { title: 'United Kingdom', value: 'uk' }
  ],
  /* OPTIONAL OPTIONS */
  limit: 10, // Limit to this number the maximum number of options visible at one time
  min: 1, // Require at least this number of options to be selected
  max: 10, // Require at most this number of options to be selected
  searchable: false, // Turn off support for filtering the list of options
  validate: values => ( values.length % 2 ) // Optional validation function
});

Each option must have the following shape:

type Option<T> = {
  /* REQUIRED VALUES */
  title: string, // The name of the option that will be showed to the user
  value: T, // The value of the option that the prompt will return you when this option is selected
  /* OPTIONAL VALUES */
  disabled?: boolean, // Whether this option can be toggled or not
  description?: string, // The description that will be showed next to the title
  heading?: boolean, // Whether this option is an unselectable heading or not
  hint?: string, // The description that will be showed next to the title, only when the item is focused
  selected?: boolean // Whether this option is pre-selected or not
};

Options can be provided dynamically also, with a function that returns an array of options depending on the current query. That can also be used to customize how results are searched and ranked.

Interactions:

| Trigger | Description | | ------- | -------------------------------------------------------------------------------------- | | Esc | Quit the prompt, which will resolve to undefined. | | Enter | Submit the selected values, checking if the selection is valid first. | | Space | Toggle the focused option. | | Up | Move the focus to the previous item, and potentially scroll to the previous page. | | Down | Move the focus to the next item, and potentially scroll to the next page. | | * | All other keys will be used to edit the search query, which will filter down the list. |

select

This prompt can be used to ask the user to pick one option between the provided ones.

import {select} from 'prask';

const result = await select ({
  /* REQUIRED OPTIONS */
  message: 'Which country would you like to visit first?', // The message that the user will read
  options: [
    { title: 'Italy', value: 'it' },
    { title: 'Turkey', value: 'tr' },
    { title: 'United Kingdom', value: 'uk' }
  ],
  /* OPTIONAL OPTIONS */
  limit: 10, // Limit to this number the maximum number of options visible at one time
  searchable: false, // Turn off support for filtering the list of options
  validate: value => value !== 'Foo' // Optional validation function
});

It can be interacted with exactly like a multiselect prompt.

boolean

This prompt can be used to ask the user to pick between yes and no.

import {boolean} from 'prask';

const result = await boolean ({
  /* REQUIRED OPTIONS */
  message: 'Do you like this library?', // The message that the user will read
  /* OPTIONAL OPTIONS */
  initial: true // Select this option by default
});

It can be interacted with exactly like a select prompt, except that search is turned off for it.

toggle

This prompt can be used to ask the user to pick between yes and no, using a single line of output, rather than the three that boolean needs.

import {toggle} from 'prask';

const result = await toggle ({
  /* REQUIRED OPTIONS */
  message: 'Do you like this library?', // The message that the user will read
  /* OPTIONAL OPTIONS */
  initial: true // Select this option by default
});

Interactions:

| Trigger | Description | | -------------------------------- | --------------------------------------------------- | | Esc | Quit the prompt, which will resolve to undefined. | | Enter | Submit the selected option. | | Left/Right/Up/Down/Tab | Select the other option. |

rating

This prompt can be used to ask the user for a numeric rating between 1 and 5.

import {rating} from 'prask';

const result = await rating ({
  /* REQUIRED OPTIONS */
  message: 'How would you rate this library?', // The message that the user will read
  /* OPTIONAL OPTIONS */
  initial: 5 // Select this option by default
});

Interactions:

| Trigger | Description | | ------------- | --------------------------------------------------- | | Esc | Quit the prompt, which will resolve to undefined. | | Enter | Submit the selected rating. | | Right/Up | Increment the selected rating. | | Left/Down | Decrement the selected rating. |

log

This utility provides functions for logging a line of text to the console, with the same style that the built-in prompts use.

import {log} from 'prask';

log.success ( 'Success message' );
log.error ( 'Error message' );
log.warning ( 'Warning message' );
log.question ( 'Question message' );
log.info ( 'Info message' );

prompt

This is a low-level general prompt, which is used internally to implement the other higher-level prompts, and which you can use to implement your own custom prompts.

import {prompt} from 'prask';
import color from 'tiny-colors';

// Let's define the "rating" prompt, it returns a number between 1 and 5, which is the rating the user picked
// This is the actual internal implementation for that component, so you can see exactly how it works

type Rating = 1 | 2 | 3 | 4 | 5;

type Options = {
  message: string,
  initial?: Rating
};

// A prompt could always resolve to "undefined", if the user escapes from it
const rating = ( options: Options ): Promise<Rating | undefined> => {

  // Let's define some internal constants we need for rendering

  const STAR_ACTIVE = color.green ( '●' );
  const STAR_INACTIVE = '○';
  const STARS_DIVIDER = '─────';
  const LABELS_DIVIDER = '     ';

  // Let's define some internal state variables
  // The status variable let's us keep track of whether this prompt was escaped (-1), submitted (1), or it's still pending (0)

  let {message, initial = 3} = options;
  let status: -1 | 0 | 1 = 0;
  let current = initial;

  // Let's define some components
  // Basically each prompt re-renders its components in a loop, React-style
  // Each prompt must return something to render, which can be a single component or an array of components
  // A component is just a function that can either return a string, which will be printed to the console, or undefined, which will be ignored
  // The "prompt" function takes care of cleaning up the previous output and replacing it with the new one
  // That's basically how the entire library works, the "prompt" function tells you when a key is pressed, and you just tell it what to render next

  const getStatusSymbol = ( status ): string => {
    if ( status < 0 ) return color.red ( '✖' );
    if ( status > 0 ) return color.green ( '✔' );
    return color.cyan.bold ( '?' );
  };

  const question = (): string => {
    const question = `${getStatusSymbol ()} ${color.bold ( message )}`;
    const result = status === 1 ? color.cyan ( String ( current ) ) : '';
    return [question, result].join ( ' ' );
  };

  const stars = (): string => {
    const star1 = ( current === 1 ) ? STAR_ACTIVE : STAR_INACTIVE;
    const star2 = ( current === 2 ) ? STAR_ACTIVE : STAR_INACTIVE;
    const star3 = ( current === 3 ) ? STAR_ACTIVE : STAR_INACTIVE;
    const star4 = ( current === 4 ) ? STAR_ACTIVE : STAR_INACTIVE;
    const star5 = ( current === 5 ) ? STAR_ACTIVE : STAR_INACTIVE;
    const stars = [star1, star2, star3, star4, star5].join ( STARS_DIVIDER );
    return stars;
  };

  const labels = (): string => {
    return [1, 2, 3, 4, 5].join ( LABELS_DIVIDER );
  };

  // Now let's handle keypresses

  return prompt ( ( resolve, {key} ) => {
    if ( key === '' ) { // Initial render, no key has been pressed yet
      return [question, stars, labels];
    } else if ( key === 'escape' ) { // Escape, let's bail out
      status = -1;
      resolve ( undefined );
      return [question];
    } else if ( key === 'return' ) { // Enter, let's return the current rating
      status = 1;
      resolve ( current );
      return [question];
    } else if ( key === 'left' || key === 'down' ) { // Left or Down, let's decrement the rating, if possible
      current = Math.max ( 1, current - 1 );
      return [question, stars, labels];
    } else if ( key === 'right' || key === 'up' ) { // Right or Up, let's increment the rating, if possible
      current = Math.min ( 5, current + 1 );
      return [question, stars, labels];
    } else { // Something else was pressed, let's just ignore it
      return [question, stars, labels];
    }
  });

};

spinner

The spinner prompt is a convenience wrapper around tiny-spinner, which you could also use directly if you prefer.

It allows to notify the user of in progress operations, and can resolve to a successful or an unsuccessful state.

import {spinner} from 'prompts';

const result = await spinner ( async ({ update, resolve, reject }) => {
  update ( 'Working' );
  await delay ( 750 );
  update ( 'Working hard' );
  await delay ( 750 );
  update ( 'Working harder' );
  await delay ( 750 );
  update ( 'Working real hard' );
  await delay ( 750 );
  let result = true; // Let's say this is the result of the operation
  if ( result ) {
    resolve ( 'Work succeeded' );
  } else {
    reject ( 'Work failed' );
  }
});

License

MIT © Fabio Spampinato