Socket
Socket
Sign inDemoInstall

@inquirer/core

Package Overview
Dependencies
Maintainers
0
Versions
82
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@inquirer/core - npm Package Compare versions

Comparing version 9.0.8 to 9.0.9

8

dist/cjs/lib/create-prompt.js

@@ -72,9 +72,3 @@ "use strict";

hooksCleanup();
if (context === null || context === void 0 ? void 0 : context.clearPromptOnDone) {
screen.clean();
}
else {
screen.clearContent();
}
screen.done();
screen.done({ clearContent: Boolean(context === null || context === void 0 ? void 0 : context.clearPromptOnDone) });
removeExitListener();

@@ -81,0 +75,0 @@ rl.input.removeListener('keypress', checkCursorPos);

56

dist/cjs/lib/screen-manager.js

@@ -11,2 +11,5 @@ "use strict";

const lastLine = (content) => { var _a; return (_a = content.split('\n').pop()) !== null && _a !== void 0 ? _a : ''; };
function cursorDown(n) {
return n > 0 ? ansi_escapes_1.default.cursorDown(n) : '';
}
class ScreenManager {

@@ -42,6 +45,9 @@ constructor(rl) {

}
write(content) {
this.rl.output.unmute();
this.rl.output.write(content);
this.rl.output.mute();
}
render(content, bottomContent = '') {
/**
* Write message to screen and setPrompt to control backspace
*/
// Write message to screen and setPrompt to control backspace
const promptLine = lastLine(content);

@@ -81,11 +87,10 @@ const rawPromptLine = (0, strip_ansi_1.default)(promptLine);

output += ansi_escapes_1.default.cursorTo(this.cursorPos.cols);
this.clean();
this.rl.output.unmute();
/**
* Set up state for next re-rendering
* Render and store state for future re-rendering
*/
this.write(cursorDown(this.extraLinesUnderPrompt) +
ansi_escapes_1.default.eraseLines(this.height) +
output);
this.extraLinesUnderPrompt = bottomContentHeight;
this.height = height(output);
this.rl.output.write(output);
this.rl.output.mute();
}

@@ -95,35 +100,12 @@ checkCursorPos() {

if (cursorPos.cols !== this.cursorPos.cols) {
this.rl.output.unmute();
this.rl.output.write(ansi_escapes_1.default.cursorTo(cursorPos.cols));
this.rl.output.mute();
this.write(ansi_escapes_1.default.cursorTo(cursorPos.cols));
this.cursorPos = cursorPos;
}
}
clean() {
this.rl.output.unmute();
this.rl.output.write([
this.extraLinesUnderPrompt > 0
? ansi_escapes_1.default.cursorDown(this.extraLinesUnderPrompt)
: '',
ansi_escapes_1.default.eraseLines(this.height),
].join(''));
this.extraLinesUnderPrompt = 0;
this.rl.output.mute();
}
clearContent() {
this.rl.output.unmute();
// Reset the cursor at the end of the previously displayed content
this.rl.output.write([
this.extraLinesUnderPrompt > 0
? ansi_escapes_1.default.cursorDown(this.extraLinesUnderPrompt)
: '',
'\n',
].join(''));
this.rl.output.mute();
}
done() {
done({ clearContent }) {
this.rl.setPrompt('');
this.rl.output.unmute();
this.rl.output.write(ansi_escapes_1.default.cursorShow);
this.rl.output.end();
let output = cursorDown(this.extraLinesUnderPrompt);
output += clearContent ? ansi_escapes_1.default.eraseLines(this.height) : '\n';
output += ansi_escapes_1.default.cursorShow;
this.write(output);
this.rl.close();

@@ -130,0 +112,0 @@ }

@@ -15,3 +15,3 @@ "use strict";

return;
signal.current(event, rl);
void signal.current(event, rl);
});

@@ -18,0 +18,0 @@ rl.input.on('keypress', handler);

import type { InquirerReadline } from '@inquirer/type';
export declare function withHooks(rl: InquirerReadline, cb: (cycle: (render: () => void) => void) => void): void;
export declare function readline(): InquirerReadline;
export declare function withUpdates<T extends (...args: any) => any>(fn: T): (...args: Parameters<T>) => ReturnType<T>;
export declare function withUpdates<R, T extends (...args: any[]) => R>(fn: T): (...args: Parameters<T>) => R;
type SetPointer<Value> = {

@@ -6,0 +6,0 @@ get(): Value;

@@ -8,7 +8,8 @@ import type { InquirerReadline } from '@inquirer/type';

constructor(rl: InquirerReadline);
write(content: string): void;
render(content: string, bottomContent?: string): void;
checkCursorPos(): void;
clean(): void;
clearContent(): void;
done(): void;
done({ clearContent }: {
clearContent: boolean;
}): void;
}
import { type InquirerReadline } from '@inquirer/type';
import { type KeypressEvent } from './key.js';
export declare function useKeypress(userHandler: (event: KeypressEvent, rl: InquirerReadline) => void): void;
export declare function useKeypress(userHandler: (event: KeypressEvent, rl: InquirerReadline) => void | Promise<void>): void;

@@ -1,4 +0,4 @@

type NotFunction<T> = T extends Function ? never : T;
type NotFunction<T> = T extends (...args: never) => unknown ? never : T;
export declare function useState<Value>(defaultValue: NotFunction<Value> | (() => Value)): [Value, (newValue: Value) => void];
export declare function useState<Value>(defaultValue?: NotFunction<Value> | (() => Value)): [Value | undefined, (newValue?: Value | undefined) => void];
export {};
{
"name": "@inquirer/core",
"version": "9.0.8",
"version": "9.0.9",
"engines": {

@@ -61,5 +61,5 @@ "node": ">=18"

"@inquirer/figures": "^1.0.5",
"@inquirer/type": "^1.5.1",
"@inquirer/type": "^1.5.2",
"@types/mute-stream": "^0.0.4",
"@types/node": "^22.0.0",
"@types/node": "^22.1.0",
"@types/wrap-ansi": "^3.0.0",

@@ -76,3 +76,3 @@ "ansi-escapes": "^4.3.2",

"devDependencies": {
"@inquirer/testing": "^2.1.30"
"@inquirer/testing": "^2.1.31"
},

@@ -101,3 +101,3 @@ "scripts": {

"sideEffects": false,
"gitHead": "8be69de6107bed7d85a7f2bfe99353d106d9c3bf"
"gitHead": "056e26aa6497e0036864d032f9eb8d821de821e7"
}

@@ -34,112 +34,132 @@ # `@inquirer/core`

## Basic concept
Visual terminal apps are at their core strings rendered onto the terminal.
The most basic prompt is a function returning a string that'll be rendered in the terminal. This function will run every time the prompt state change, and the new returned string will replace the previously rendered one. The prompt cursor appears after the string.
Wrapping the rendering function with `createPrompt()` will setup the rendering layer, inject the state management utilities, and wait until the `done` callback is called.
```ts
import colors from 'yoctocolors';
import {
createPrompt,
useState,
useKeypress,
isEnterKey,
usePrefix,
} from '@inquirer/core';
import { createPrompt } from '@inquirer/core';
const confirm = createPrompt<boolean, { message: string; default?: boolean }>(
(config, done) => {
const [status, setStatus] = useState('pending');
const [value, setValue] = useState('');
const prefix = usePrefix({});
const input = createPrompt((config, done) => {
// Implement logic
useKeypress((key, rl) => {
if (isEnterKey(key)) {
const answer = value ? /^y(es)?/i.test(value) : config.default !== false;
setValue(answer ? 'yes' : 'no');
setStatus('done');
done(answer);
} else {
setValue(rl.line);
}
});
return '? My question';
});
let formattedValue = value;
let defaultValue = '';
if (status === 'done') {
formattedValue = colors.cyan(value);
} else {
defaultValue = colors.dim(config.default === false ? ' (y/N)' : ' (Y/n)');
// And it is then called as
const answer = await input({
/* config */
});
```
## Hooks
State management and user interactions are handled through hooks. Hooks are common [within the React ecosystem](https://react.dev/reference/react/hooks), and Inquirer reimplement the common ones.
### State hook
State lets a component “remember” information like user input. For example, an input prompt can use state to store the input value, while a list prompt can use state to track the cursor index.
`useState` declares a state variable that you can update directly.
```ts
import { createPrompt, useState } from '@inquirer/core';
const input = createPrompt((config, done) => {
const [index, setIndex] = useState(0);
// ...
```
### Keypress hook
Almost all prompts need to react to user actions. In a terminal, this is done through typing.
`useKeypress` allows you to react to keypress events, and access the prompt line.
```ts
const input = createPrompt((config, done) => {
useKeypress((key) => {
if (key.name === 'enter') {
done(answer);
}
});
const message = colors.bold(config.message);
return `${prefix} ${message}${defaultValue} ${formattedValue}`;
},
);
// ...
```
/**
* Which then can be used like this:
*/
const answer = await confirm({ message: 'Do you want to continue?' });
Behind the scenes, Inquirer prompts are wrappers around [readlines](https://nodejs.org/api/readline.html). Aside the keypress event object, the hook also pass the active readline instance to the event handler.
```ts
const input = createPrompt((config, done) => {
useKeypress((key, readline) => {
setValue(readline.line);
});
// ...
```
See more examples:
### Ref hook
- [Confirm Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/confirm/src/index.mts)
- [Input Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/input/src/index.mts)
- [Password Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/password/src/index.mts)
- [Editor Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/editor/src/index.mts)
- [Select Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/select/src/index.mts)
- [Checkbox Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/checkbox/src/index.mts)
- [Rawlist Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/rawlist/src/index.mts)
- [Expand Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/expand/src/index.mts)
Refs let a prompt hold some information that isn’t used for rendering, like a class instance or a timeout ID. Unlike with state, updating a ref does not re-render your prompt. Refs are an “escape hatch” from the rendering paradigm.
## API
`useRef` declares a ref. You can hold any value in it, but most often it’s used to hold a timeout ID.
### `createPrompt(viewFn)`
```ts
const input = createPrompt((config, done) => {
const timeout = useRef(null);
The `createPrompt` function returns an asynchronous function that returns a cancelable promise resolving to the valid answer a user submit. This prompt function takes the prompt configuration as its first argument (this is defined by each prompt), and the context options as a second argument.
// ...
```
The prompt configuration is unique to each prompt. The context options are:
### Effect Hook
| Property | Type | Required | Description |
| ----------------- | ----------------------- | -------- | ------------------------------------------------------------ |
| input | `NodeJS.ReadableStream` | no | The stdin stream (defaults to `process.stdin`) |
| output | `NodeJS.WritableStream` | no | The stdout stream (defaults to `process.stdout`) |
| clearPromptOnDone | `boolean` | no | If true, we'll clear the screen after the prompt is answered |
Effects let a prompt connect to and synchronize with external systems. This includes dealing with network or animations.
The cancelable promise exposes a `cancel` method that'll exit the prompt and reject the promise.
`useEffect` connects a component to an external system.
#### Typescript
```ts
const chat = createPrompt((config, done) => {
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]);
If using typescript, `createPrompt` takes 2 generic arguments (ex `createPrompt<string, { message: string }>()`)
// ...
```
The first one is the type of the resolved value; `function createPrompt<Value>(): Promise<Value> {}`
### Performance hook
The second one is the type of the prompt config; in other words the interface the created prompt will provide to users.
A common way to optimize re-rendering performance is to skip unnecessary work. For example, you can tell Inquirer to reuse a cached calculation or to skip a re-render if the data has not changed since the previous render.
### Hooks
`useMemo` lets you cache the result of an expensive calculation.
Hooks can only be called within the prompt function and are used to handle state and events.
```ts
const todoSelect = createPrompt((config, done) => {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
Those hooks are matching the React hooks API:
// ...
```
- `useState`
- `useRef`
- `useEffect`
- `useMemo`
### Rendering hooks
And those are custom utilities from Inquirer:
#### Prefix / loading
- `useKeypress`
- `usePagination`
- `usePrefix`
All default prompts, and most custom ones, uses a prefix at the beginning of the prompt line. This helps visually delineate different questions, and provides a convenient area to render a loading spinner.
### Key utilities
`usePrefix` is a built-in hook to do this.
Listening for keypress events inside an inquirer prompt is a very common pattern. To ease this, we export a few utility functions taking in the keypress event object and return a boolean:
```ts
const input = createPrompt((config, done) => {
const prefix = usePrefix({ isLoading });
- `isEnterKey()`
- `isBackspaceKey()`
- `isSpaceKey()`
- `isUpKey()` - Note: this utility will handle vim and emacs keybindings (up, `k`, and `ctrl+p`)
- `isDownKey()` - Note: this utility will handle vim and emacs keybindings (down, `j`, and `ctrl+n`)
- `isNumberKey()` one of 1, 2, 3, 4, 5, 6, 7, 8, 9, 0
return `${prefix} My question`;
});
```
### `usePagination`
#### Pagination

@@ -168,4 +188,68 @@ When looping through a long list of options (like in the `select` prompt), paginating the results appearing on the screen at once can be necessary. The `usePagination` hook is the utility used within the `select` and `checkbox` prompts to cycle through the list of options.

### Theming
## `createPrompt()` API
As we saw earlier, the rendering function should return a string, and eventually call `done` to close the prompt and return the answer.
```ts
const input = createPrompt((config, done) => {
const [value, setValue] = useState();
useKeypress((key, readline) => {
if (key.name === 'enter') {
done(answer);
} else {
setValue(readline.line);
}
});
return `? ${config.message} ${value}`;
});
```
The rendering function can also return a tuple of 2 string (`[string, string]`.) The first string represents the prompt. The second one is content to render under the prompt, like an error message. The text input cursor will appear after the first string.
```ts
const number = createPrompt((config, done) => {
// Add some logic here
return [`? My question ${input}`, `! The input must be a number`];
});
```
### Typescript
If using typescript, `createPrompt` takes 2 generic arguments.
```ts
// createPrompt<Value, Config>
const input = createPrompt<string, { message: string }>(// ...
```
The first one is the type of the resolved value
```ts
const answer: string = await input();
```
The second one is the type of the prompt config; in other words the interface the created prompt will provide to users.
```ts
const answer = await input({
message: 'My question',
});
```
## Key utilities
Listening for keypress events inside an inquirer prompt is a very common pattern. To ease this, we export a few utility functions taking in the keypress event object and return a boolean:
- `isEnterKey()`
- `isBackspaceKey()`
- `isSpaceKey()`
- `isUpKey()` - Note: this utility will handle vim and emacs keybindings (up, `k`, and `ctrl+p`)
- `isDownKey()` - Note: this utility will handle vim and emacs keybindings (down, `j`, and `ctrl+n`)
- `isNumberKey()` one of 1, 2, 3, 4, 5, 6, 7, 8, 9, 0
## Theming
Theming utilities will allow you to expose customization of the prompt style. Inquirer also has a few standard theme values shared across all the official prompts.

@@ -238,2 +322,61 @@

# Examples
You can refer to any `@inquirer/prompts` prompts for real examples:
- [Confirm Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/confirm/src/index.mts)
- [Input Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/input/src/index.mts)
- [Password Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/password/src/index.mts)
- [Editor Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/editor/src/index.mts)
- [Select Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/select/src/index.mts)
- [Checkbox Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/checkbox/src/index.mts)
- [Rawlist Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/rawlist/src/index.mts)
- [Expand Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/expand/src/index.mts)
```ts
import colors from 'yoctocolors';
import {
createPrompt,
useState,
useKeypress,
isEnterKey,
usePrefix,
} from '@inquirer/core';
const confirm = createPrompt<boolean, { message: string; default?: boolean }>(
(config, done) => {
const [status, setStatus] = useState('pending');
const [value, setValue] = useState('');
const prefix = usePrefix({});
useKeypress((key, rl) => {
if (isEnterKey(key)) {
const answer = value ? /^y(es)?/i.test(value) : config.default !== false;
setValue(answer ? 'yes' : 'no');
setStatus('done');
done(answer);
} else {
setValue(rl.line);
}
});
let formattedValue = value;
let defaultValue = '';
if (status === 'done') {
formattedValue = colors.cyan(value);
} else {
defaultValue = colors.dim(config.default === false ? ' (y/N)' : ' (Y/n)');
}
const message = colors.bold(config.message);
return `${prefix} ${message}${defaultValue} ${formattedValue}`;
},
);
/**
* Which then can be used like this:
*/
const answer = await confirm({ message: 'Do you want to continue?' });
```
# License

@@ -240,0 +383,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc