Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

@inquirer/core

Package Overview
Dependencies
Maintainers
2
Versions
115
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
11.0.0
to
11.0.1
+11
-16
package.json
{
"name": "@inquirer/core",
"version": "11.0.0",
"version": "11.0.1",
"description": "Core Inquirer prompt API",

@@ -55,3 +55,6 @@ "keywords": [

"./package.json": "./package.json",
".": "./src/index.ts"
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},

@@ -65,5 +68,5 @@ "files": [

"dependencies": {
"@inquirer/ansi": "^2.0.0",
"@inquirer/figures": "^2.0.0",
"@inquirer/type": "^4.0.0",
"@inquirer/ansi": "^2.0.1",
"@inquirer/figures": "^2.0.1",
"@inquirer/type": "^4.0.1",
"cli-width": "^4.1.0",

@@ -75,4 +78,3 @@ "mute-stream": "^3.0.0",

"devDependencies": {
"@inquirer/testing": "^3.0.0",
"@repo/tsconfig": "0.0.0",
"@inquirer/testing": "^3.0.1",
"@types/mute-stream": "^0.0.4",

@@ -94,12 +96,5 @@ "@types/node": "^24.10.1",

"publishConfig": {
"access": "public",
"exports": {
"./package.json": "./package.json",
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
"access": "public"
},
"gitHead": "676685d33374a30340c1b9f0831c7eae2b2357dd"
"gitHead": "cce79ce3b9bbdfb4dbb798078cf3b94b9adc7d1b"
}
export { isUpKey, isDownKey, isSpaceKey, isBackspaceKey, isTabKey, isNumberKey, isEnterKey, type KeypressEvent, type Keybinding, } from './lib/key.ts';
export * from './lib/errors.ts';
export { usePrefix } from './lib/use-prefix.ts';
export { useState } from './lib/use-state.ts';
export { useEffect } from './lib/use-effect.ts';
export { useMemo } from './lib/use-memo.ts';
export { useRef } from './lib/use-ref.ts';
export { useKeypress } from './lib/use-keypress.ts';
export { makeTheme } from './lib/make-theme.ts';
export type { Theme, Status } from './lib/theme.ts';
export { usePagination } from './lib/pagination/use-pagination.ts';
export { createPrompt } from './lib/create-prompt.ts';
export { Separator } from './lib/Separator.ts';
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Separator = exports.createPrompt = exports.usePagination = exports.makeTheme = exports.useKeypress = exports.useRef = exports.useMemo = exports.useEffect = exports.useState = exports.usePrefix = exports.isEnterKey = exports.isNumberKey = exports.isTabKey = exports.isBackspaceKey = exports.isSpaceKey = exports.isDownKey = exports.isUpKey = void 0;
var key_ts_1 = require("./lib/key.js");
Object.defineProperty(exports, "isUpKey", { enumerable: true, get: function () { return key_ts_1.isUpKey; } });
Object.defineProperty(exports, "isDownKey", { enumerable: true, get: function () { return key_ts_1.isDownKey; } });
Object.defineProperty(exports, "isSpaceKey", { enumerable: true, get: function () { return key_ts_1.isSpaceKey; } });
Object.defineProperty(exports, "isBackspaceKey", { enumerable: true, get: function () { return key_ts_1.isBackspaceKey; } });
Object.defineProperty(exports, "isTabKey", { enumerable: true, get: function () { return key_ts_1.isTabKey; } });
Object.defineProperty(exports, "isNumberKey", { enumerable: true, get: function () { return key_ts_1.isNumberKey; } });
Object.defineProperty(exports, "isEnterKey", { enumerable: true, get: function () { return key_ts_1.isEnterKey; } });
__exportStar(require("./lib/errors.js"), exports);
var use_prefix_ts_1 = require("./lib/use-prefix.js");
Object.defineProperty(exports, "usePrefix", { enumerable: true, get: function () { return use_prefix_ts_1.usePrefix; } });
var use_state_ts_1 = require("./lib/use-state.js");
Object.defineProperty(exports, "useState", { enumerable: true, get: function () { return use_state_ts_1.useState; } });
var use_effect_ts_1 = require("./lib/use-effect.js");
Object.defineProperty(exports, "useEffect", { enumerable: true, get: function () { return use_effect_ts_1.useEffect; } });
var use_memo_ts_1 = require("./lib/use-memo.js");
Object.defineProperty(exports, "useMemo", { enumerable: true, get: function () { return use_memo_ts_1.useMemo; } });
var use_ref_ts_1 = require("./lib/use-ref.js");
Object.defineProperty(exports, "useRef", { enumerable: true, get: function () { return use_ref_ts_1.useRef; } });
var use_keypress_ts_1 = require("./lib/use-keypress.js");
Object.defineProperty(exports, "useKeypress", { enumerable: true, get: function () { return use_keypress_ts_1.useKeypress; } });
var make_theme_ts_1 = require("./lib/make-theme.js");
Object.defineProperty(exports, "makeTheme", { enumerable: true, get: function () { return make_theme_ts_1.makeTheme; } });
var use_pagination_ts_1 = require("./lib/pagination/use-pagination.js");
Object.defineProperty(exports, "usePagination", { enumerable: true, get: function () { return use_pagination_ts_1.usePagination; } });
var create_prompt_ts_1 = require("./lib/create-prompt.js");
Object.defineProperty(exports, "createPrompt", { enumerable: true, get: function () { return create_prompt_ts_1.createPrompt; } });
var Separator_ts_1 = require("./lib/Separator.js");
Object.defineProperty(exports, "Separator", { enumerable: true, get: function () { return Separator_ts_1.Separator; } });
import { type Prompt, type Prettify } from '@inquirer/type';
type ViewFunction<Value, Config> = (config: Prettify<Config>, done: (value: Value) => void) => string | [string, string | undefined];
export declare function createPrompt<Value, Config>(view: ViewFunction<Value, Config>): Prompt<Value, Config>;
export {};
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPrompt = createPrompt;
const readline = __importStar(require("node:readline"));
const node_async_hooks_1 = require("node:async_hooks");
const mute_stream_1 = __importDefault(require("mute-stream"));
const signal_exit_1 = require("signal-exit");
const screen_manager_ts_1 = __importDefault(require("./screen-manager.js"));
const promise_polyfill_ts_1 = require("./promise-polyfill.js");
const hook_engine_ts_1 = require("./hook-engine.js");
const errors_ts_1 = require("./errors.js");
function getCallSites() {
// eslint-disable-next-line @typescript-eslint/unbound-method
const _prepareStackTrace = Error.prepareStackTrace;
let result = [];
try {
Error.prepareStackTrace = (_, callSites) => {
const callSitesWithoutCurrent = callSites.slice(1);
result = callSitesWithoutCurrent;
return callSitesWithoutCurrent;
};
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
new Error().stack;
}
catch {
// An error will occur if the Node flag --frozen-intrinsics is used.
// https://nodejs.org/api/cli.html#--frozen-intrinsics
return result;
}
Error.prepareStackTrace = _prepareStackTrace;
return result;
}
function createPrompt(view) {
const callSites = getCallSites();
const prompt = (config, context = {}) => {
// Default `input` to stdin
const { input = process.stdin, signal } = context;
const cleanups = new Set();
// Add mute capabilities to the output
const output = new mute_stream_1.default();
output.pipe(context.output ?? process.stdout);
const rl = readline.createInterface({
terminal: true,
input,
output,
});
const screen = new screen_manager_ts_1.default(rl);
const { promise, resolve, reject } = promise_polyfill_ts_1.PromisePolyfill.withResolver();
const cancel = () => reject(new errors_ts_1.CancelPromptError());
if (signal) {
const abort = () => reject(new errors_ts_1.AbortPromptError({ cause: signal.reason }));
if (signal.aborted) {
abort();
return Object.assign(promise, { cancel });
}
signal.addEventListener('abort', abort);
cleanups.add(() => signal.removeEventListener('abort', abort));
}
cleanups.add((0, signal_exit_1.onExit)((code, signal) => {
reject(new errors_ts_1.ExitPromptError(`User force closed the prompt with ${code} ${signal}`));
}));
// SIGINT must be explicitly handled by the prompt so the ExitPromptError can be handled.
// Otherwise, the prompt will stop and in some scenarios never resolve.
// Ref issue #1741
const sigint = () => reject(new errors_ts_1.ExitPromptError(`User force closed the prompt with SIGINT`));
rl.on('SIGINT', sigint);
cleanups.add(() => rl.removeListener('SIGINT', sigint));
// Re-renders only happen when the state change; but the readline cursor could change position
// and that also requires a re-render (and a manual one because we mute the streams).
// We set the listener after the initial workLoop to avoid a double render if render triggered
// by a state change sets the cursor to the right position.
const checkCursorPos = () => screen.checkCursorPos();
rl.input.on('keypress', checkCursorPos);
cleanups.add(() => rl.input.removeListener('keypress', checkCursorPos));
return (0, hook_engine_ts_1.withHooks)(rl, (cycle) => {
// The close event triggers immediately when the user press ctrl+c. SignalExit on the other hand
// triggers after the process is done (which happens after timeouts are done triggering.)
// We triggers the hooks cleanup phase on rl `close` so active timeouts can be cleared.
const hooksCleanup = node_async_hooks_1.AsyncResource.bind(() => hook_engine_ts_1.effectScheduler.clearAll());
rl.on('close', hooksCleanup);
cleanups.add(() => rl.removeListener('close', hooksCleanup));
cycle(() => {
try {
const nextView = view(config, (value) => {
setImmediate(() => resolve(value));
});
// Typescript won't allow this, but not all users rely on typescript.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (nextView === undefined) {
const callerFilename = callSites[1]?.getFileName();
throw new Error(`Prompt functions must return a string.\n at ${callerFilename}`);
}
const [content, bottomContent] = typeof nextView === 'string' ? [nextView] : nextView;
screen.render(content, bottomContent);
hook_engine_ts_1.effectScheduler.run();
}
catch (error) {
reject(error);
}
});
return Object.assign(promise
.then((answer) => {
hook_engine_ts_1.effectScheduler.clearAll();
return answer;
}, (error) => {
hook_engine_ts_1.effectScheduler.clearAll();
throw error;
})
// Wait for the promise to settle, then cleanup.
.finally(() => {
cleanups.forEach((cleanup) => cleanup());
screen.done({ clearContent: Boolean(context.clearPromptOnDone) });
output.end();
})
// Once cleanup is done, let the expose promise resolve/reject to the internal one.
.then(() => promise), { cancel });
});
};
return prompt;
}
export declare class AbortPromptError extends Error {
name: string;
message: string;
constructor(options?: {
cause?: unknown;
});
}
export declare class CancelPromptError extends Error {
name: string;
message: string;
}
export declare class ExitPromptError extends Error {
name: string;
}
export declare class HookError extends Error {
name: string;
}
export declare class ValidationError extends Error {
name: string;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValidationError = exports.HookError = exports.ExitPromptError = exports.CancelPromptError = exports.AbortPromptError = void 0;
class AbortPromptError extends Error {
name = 'AbortPromptError';
message = 'Prompt was aborted';
constructor(options) {
super();
this.cause = options?.cause;
}
}
exports.AbortPromptError = AbortPromptError;
class CancelPromptError extends Error {
name = 'CancelPromptError';
message = 'Prompt was canceled';
}
exports.CancelPromptError = CancelPromptError;
class ExitPromptError extends Error {
name = 'ExitPromptError';
}
exports.ExitPromptError = ExitPromptError;
class HookError extends Error {
name = 'HookError';
}
exports.HookError = HookError;
class ValidationError extends Error {
name = 'ValidationError';
}
exports.ValidationError = ValidationError;
import type { InquirerReadline } from '@inquirer/type';
export declare function withHooks<T>(rl: InquirerReadline, cb: (cycle: (render: () => void) => void) => T): T;
export declare function readline(): InquirerReadline;
export declare function withUpdates<Args extends unknown[], R>(fn: (...args: Args) => R): (...args: Args) => R;
type SetPointer<Value> = {
get(): Value;
set(value: Value): void;
initialized: true;
};
type UnsetPointer<Value> = {
get(): void;
set(value: Value): void;
initialized: false;
};
type Pointer<Value> = SetPointer<Value> | UnsetPointer<Value>;
export declare function withPointer<Value, ReturnValue>(cb: (pointer: Pointer<Value>) => ReturnValue): ReturnValue;
export declare function handleChange(): void;
export declare const effectScheduler: {
queue(cb: (readline: InquirerReadline) => void | (() => void)): void;
run(): void;
clearAll(): void;
};
export {};
"use strict";
/* eslint @typescript-eslint/no-explicit-any: ["off"] */
Object.defineProperty(exports, "__esModule", { value: true });
exports.effectScheduler = void 0;
exports.withHooks = withHooks;
exports.readline = readline;
exports.withUpdates = withUpdates;
exports.withPointer = withPointer;
exports.handleChange = handleChange;
const node_async_hooks_1 = require("node:async_hooks");
const errors_ts_1 = require("./errors.js");
const hookStorage = new node_async_hooks_1.AsyncLocalStorage();
function createStore(rl) {
const store = {
rl,
hooks: [],
hooksCleanup: [],
hooksEffect: [],
index: 0,
handleChange() { },
};
return store;
}
// Run callback in with the hook engine setup.
function withHooks(rl, cb) {
const store = createStore(rl);
return hookStorage.run(store, () => {
function cycle(render) {
store.handleChange = () => {
store.index = 0;
render();
};
store.handleChange();
}
return cb(cycle);
});
}
// Safe getStore utility that'll return the store or throw if undefined.
function getStore() {
const store = hookStorage.getStore();
if (!store) {
throw new errors_ts_1.HookError('[Inquirer] Hook functions can only be called from within a prompt');
}
return store;
}
function readline() {
return getStore().rl;
}
// Merge state updates happening within the callback function to avoid multiple renders.
function withUpdates(fn) {
const wrapped = (...args) => {
const store = getStore();
let shouldUpdate = false;
const oldHandleChange = store.handleChange;
store.handleChange = () => {
shouldUpdate = true;
};
const returnValue = fn(...args);
if (shouldUpdate) {
oldHandleChange();
}
store.handleChange = oldHandleChange;
return returnValue;
};
return node_async_hooks_1.AsyncResource.bind(wrapped);
}
function withPointer(cb) {
const store = getStore();
const { index } = store;
const pointer = {
get() {
return store.hooks[index];
},
set(value) {
store.hooks[index] = value;
},
initialized: index in store.hooks,
};
const returnValue = cb(pointer);
store.index++;
return returnValue;
}
function handleChange() {
getStore().handleChange();
}
exports.effectScheduler = {
queue(cb) {
const store = getStore();
const { index } = store;
store.hooksEffect.push(() => {
store.hooksCleanup[index]?.();
const cleanFn = cb(readline());
if (cleanFn != null && typeof cleanFn !== 'function') {
throw new errors_ts_1.ValidationError('useEffect return value must be a cleanup function or nothing.');
}
store.hooksCleanup[index] = cleanFn;
});
},
run() {
const store = getStore();
withUpdates(() => {
store.hooksEffect.forEach((effect) => {
effect();
});
// Warning: Clean the hooks before exiting the `withUpdates` block.
// Failure to do so means an updates would hit the same effects again.
store.hooksEffect.length = 0;
})();
},
clearAll() {
const store = getStore();
store.hooksCleanup.forEach((cleanFn) => {
cleanFn?.();
});
store.hooksEffect.length = 0;
store.hooksCleanup.length = 0;
},
};
export type KeypressEvent = {
name: string;
ctrl: boolean;
};
export type Keybinding = 'emacs' | 'vim';
export declare const isUpKey: (key: KeypressEvent, keybindings?: ReadonlyArray<Keybinding>) => boolean;
export declare const isDownKey: (key: KeypressEvent, keybindings?: ReadonlyArray<Keybinding>) => boolean;
export declare const isSpaceKey: (key: KeypressEvent) => boolean;
export declare const isBackspaceKey: (key: KeypressEvent) => boolean;
export declare const isTabKey: (key: KeypressEvent) => boolean;
export declare const isNumberKey: (key: KeypressEvent) => boolean;
export declare const isEnterKey: (key: KeypressEvent) => boolean;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isEnterKey = exports.isNumberKey = exports.isTabKey = exports.isBackspaceKey = exports.isSpaceKey = exports.isDownKey = exports.isUpKey = void 0;
const isUpKey = (key, keybindings = []) =>
// The up key
key.name === 'up' ||
// Vim keybinding: hjkl keys map to left/down/up/right
(keybindings.includes('vim') && key.name === 'k') ||
// Emacs keybinding: Ctrl+P means "previous" in Emacs navigation conventions
(keybindings.includes('emacs') && key.ctrl && key.name === 'p');
exports.isUpKey = isUpKey;
const isDownKey = (key, keybindings = []) =>
// The down key
key.name === 'down' ||
// Vim keybinding: hjkl keys map to left/down/up/right
(keybindings.includes('vim') && key.name === 'j') ||
// Emacs keybinding: Ctrl+N means "next" in Emacs navigation conventions
(keybindings.includes('emacs') && key.ctrl && key.name === 'n');
exports.isDownKey = isDownKey;
const isSpaceKey = (key) => key.name === 'space';
exports.isSpaceKey = isSpaceKey;
const isBackspaceKey = (key) => key.name === 'backspace';
exports.isBackspaceKey = isBackspaceKey;
const isTabKey = (key) => key.name === 'tab';
exports.isTabKey = isTabKey;
const isNumberKey = (key) => '1234567890'.includes(key.name);
exports.isNumberKey = isNumberKey;
const isEnterKey = (key) => key.name === 'enter' || key.name === 'return';
exports.isEnterKey = isEnterKey;
import type { Prettify, PartialDeep } from '@inquirer/type';
import { type Theme } from './theme.ts';
export declare function makeTheme<SpecificTheme extends object>(...themes: ReadonlyArray<undefined | PartialDeep<Theme<SpecificTheme>>>): Prettify<Theme<SpecificTheme>>;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeTheme = makeTheme;
const theme_ts_1 = require("./theme.js");
function isPlainObject(value) {
if (typeof value !== 'object' || value === null)
return false;
let proto = value;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(value) === proto;
}
function deepMerge(...objects) {
const output = {};
for (const obj of objects) {
for (const [key, value] of Object.entries(obj)) {
const prevValue = output[key];
output[key] =
isPlainObject(prevValue) && isPlainObject(value)
? deepMerge(prevValue, value)
: value;
}
}
return output;
}
function makeTheme(...themes) {
const themesToMerge = [
theme_ts_1.defaultTheme,
...themes.filter((theme) => theme != null),
];
return deepMerge(...themesToMerge);
}
import type { Prettify } from '@inquirer/type';
export declare function usePagination<T>({ items, active, renderItem, pageSize, loop, }: {
items: ReadonlyArray<T>;
/** The index of the active item. */
active: number;
/** Renders an item as part of a page. */
renderItem: (layout: Prettify<{
item: T;
index: number;
isActive: boolean;
}>) => string;
/** The size of the page. */
pageSize: number;
/** Allows creating an infinitely looping list. `true` if unspecified. */
loop?: boolean;
}): string;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.usePagination = usePagination;
const use_ref_ts_1 = require("../use-ref.js");
const utils_ts_1 = require("../utils.js");
function usePointerPosition({ active, renderedItems, pageSize, loop, }) {
const state = (0, use_ref_ts_1.useRef)({
lastPointer: active,
lastActive: undefined,
});
const { lastPointer, lastActive } = state.current;
const middle = Math.floor(pageSize / 2);
const renderedLength = renderedItems.reduce((acc, item) => acc + item.length, 0);
const defaultPointerPosition = renderedItems
.slice(0, active)
.reduce((acc, item) => acc + item.length, 0);
let pointer = defaultPointerPosition;
if (renderedLength > pageSize) {
if (loop) {
/**
* Creates the next position for the pointer considering an infinitely
* looping list of items to be rendered on the page.
*
* The goal is to progressively move the cursor to the middle position as the user move down, and then keep
* the cursor there. When the user move up, maintain the cursor position.
*/
// By default, keep the cursor position as-is.
pointer = lastPointer;
if (
// First render, skip this logic.
lastActive != null &&
// Only move the pointer down when the user moves down.
lastActive < active &&
// Check user didn't move up across page boundary.
active - lastActive < pageSize) {
pointer = Math.min(
// Furthest allowed position for the pointer is the middle of the list
middle, Math.abs(active - lastActive) === 1
? Math.min(
// Move the pointer at most the height of the last active item.
lastPointer + (renderedItems[lastActive]?.length ?? 0),
// If the user moved by one item, move the pointer to the natural position of the active item as
// long as it doesn't move the cursor up.
Math.max(defaultPointerPosition, lastPointer))
: // Otherwise, move the pointer down by the difference between the active and last active item.
lastPointer + active - lastActive);
}
}
else {
/**
* Creates the next position for the pointer considering a finite list of
* items to be rendered on a page.
*
* The goal is to keep the pointer in the middle of the page whenever possible, until
* we reach the bounds of the list (top or bottom). In which case, the cursor moves progressively
* to the bottom or top of the list.
*/
const spaceUnderActive = renderedItems
.slice(active)
.reduce((acc, item) => acc + item.length, 0);
pointer =
spaceUnderActive < pageSize - middle
? // If the active item is near the end of the list, progressively move the cursor towards the end.
pageSize - spaceUnderActive
: // Otherwise, progressively move the pointer to the middle of the list.
Math.min(defaultPointerPosition, middle);
}
}
// Save state for the next render
state.current.lastPointer = pointer;
state.current.lastActive = active;
return pointer;
}
function usePagination({ items, active, renderItem, pageSize, loop = true, }) {
const width = (0, utils_ts_1.readlineWidth)();
const bound = (num) => ((num % items.length) + items.length) % items.length;
const renderedItems = items.map((item, index) => {
if (item == null)
return [];
return (0, utils_ts_1.breakLines)(renderItem({ item, index, isActive: index === active }), width).split('\n');
});
const renderedLength = renderedItems.reduce((acc, item) => acc + item.length, 0);
const renderItemAtIndex = (index) => renderedItems[index] ?? [];
const pointer = usePointerPosition({ active, renderedItems, pageSize, loop });
// Render the active item to decide the position.
// If the active item fits under the pointer, we render it there.
// Otherwise, we need to render it to fit at the bottom of the page; moving the pointer up.
const activeItem = renderItemAtIndex(active).slice(0, pageSize);
const activeItemPosition = pointer + activeItem.length <= pageSize ? pointer : pageSize - activeItem.length;
// Create an array of lines for the page, and add the lines of the active item into the page
const pageBuffer = Array.from({ length: pageSize });
pageBuffer.splice(activeItemPosition, activeItem.length, ...activeItem);
// Store to prevent rendering the same item twice
const itemVisited = new Set([active]);
// Fill the page under the active item
let bufferPointer = activeItemPosition + activeItem.length;
let itemPointer = bound(active + 1);
while (bufferPointer < pageSize &&
!itemVisited.has(itemPointer) &&
(loop && renderedLength > pageSize ? itemPointer !== active : itemPointer > active)) {
const lines = renderItemAtIndex(itemPointer);
const linesToAdd = lines.slice(0, pageSize - bufferPointer);
pageBuffer.splice(bufferPointer, linesToAdd.length, ...linesToAdd);
// Move pointers for next iteration
itemVisited.add(itemPointer);
bufferPointer += linesToAdd.length;
itemPointer = bound(itemPointer + 1);
}
// Fill the page over the active item
bufferPointer = activeItemPosition - 1;
itemPointer = bound(active - 1);
while (bufferPointer >= 0 &&
!itemVisited.has(itemPointer) &&
(loop && renderedLength > pageSize ? itemPointer !== active : itemPointer < active)) {
const lines = renderItemAtIndex(itemPointer);
const linesToAdd = lines.slice(Math.max(0, lines.length - bufferPointer - 1));
pageBuffer.splice(bufferPointer - linesToAdd.length + 1, linesToAdd.length, ...linesToAdd);
// Move pointers for next iteration
itemVisited.add(itemPointer);
bufferPointer -= linesToAdd.length;
itemPointer = bound(itemPointer - 1);
}
return pageBuffer.filter((line) => typeof line === 'string').join('\n');
}
export declare class PromisePolyfill<T> extends Promise<T> {
static withResolver<T>(): {
promise: Promise<T>;
resolve: (value: T) => void;
reject: (error: unknown) => void;
};
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PromisePolyfill = void 0;
// TODO: Remove this class once Node 22 becomes the minimum supported version.
class PromisePolyfill extends Promise {
// Available starting from Node 22
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers
static withResolver() {
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve: resolve, reject: reject };
}
}
exports.PromisePolyfill = PromisePolyfill;
import type { InquirerReadline } from '@inquirer/type';
export default class ScreenManager {
private height;
private extraLinesUnderPrompt;
private cursorPos;
private readonly rl;
constructor(rl: InquirerReadline);
write(content: string): void;
render(content: string, bottomContent?: string): void;
checkCursorPos(): void;
done({ clearContent }: {
clearContent: boolean;
}): void;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const node_util_1 = require("node:util");
const utils_ts_1 = require("./utils.js");
const ansi_1 = require("@inquirer/ansi");
const height = (content) => content.split('\n').length;
const lastLine = (content) => content.split('\n').pop() ?? '';
class ScreenManager {
// These variables are keeping information to allow correct prompt re-rendering
height = 0;
extraLinesUnderPrompt = 0;
cursorPos;
rl;
constructor(rl) {
this.rl = rl;
this.cursorPos = rl.getCursorPos();
}
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
const promptLine = lastLine(content);
const rawPromptLine = (0, node_util_1.stripVTControlCharacters)(promptLine);
// Remove the rl.line from our prompt. We can't rely on the content of
// rl.line (mainly because of the password prompt), so just rely on it's
// length.
let prompt = rawPromptLine;
if (this.rl.line.length > 0) {
prompt = prompt.slice(0, -this.rl.line.length);
}
this.rl.setPrompt(prompt);
// SetPrompt will change cursor position, now we can get correct value
this.cursorPos = this.rl.getCursorPos();
const width = (0, utils_ts_1.readlineWidth)();
content = (0, utils_ts_1.breakLines)(content, width);
bottomContent = (0, utils_ts_1.breakLines)(bottomContent, width);
// Manually insert an extra line if we're at the end of the line.
// This prevent the cursor from appearing at the beginning of the
// current line.
if (rawPromptLine.length % width === 0) {
content += '\n';
}
let output = content + (bottomContent ? '\n' + bottomContent : '');
/**
* Re-adjust the cursor at the correct position.
*/
// We need to consider parts of the prompt under the cursor as part of the bottom
// content in order to correctly cleanup and re-render.
const promptLineUpDiff = Math.floor(rawPromptLine.length / width) - this.cursorPos.rows;
const bottomContentHeight = promptLineUpDiff + (bottomContent ? height(bottomContent) : 0);
// Return cursor to the input position (on top of the bottomContent)
if (bottomContentHeight > 0)
output += (0, ansi_1.cursorUp)(bottomContentHeight);
// Return cursor to the initial left offset.
output += (0, ansi_1.cursorTo)(this.cursorPos.cols);
/**
* Render and store state for future re-rendering
*/
this.write((0, ansi_1.cursorDown)(this.extraLinesUnderPrompt) + (0, ansi_1.eraseLines)(this.height) + output);
this.extraLinesUnderPrompt = bottomContentHeight;
this.height = height(output);
}
checkCursorPos() {
const cursorPos = this.rl.getCursorPos();
if (cursorPos.cols !== this.cursorPos.cols) {
this.write((0, ansi_1.cursorTo)(cursorPos.cols));
this.cursorPos = cursorPos;
}
}
done({ clearContent }) {
this.rl.setPrompt('');
let output = (0, ansi_1.cursorDown)(this.extraLinesUnderPrompt);
output += clearContent ? (0, ansi_1.eraseLines)(this.height) : '\n';
output += ansi_1.cursorShow;
this.write(output);
this.rl.close();
}
}
exports.default = ScreenManager;
/**
* Separator object
* Used to space/separate choices group
*/
export declare class Separator {
readonly separator: string;
readonly type: string;
constructor(separator?: string);
static isSeparator(choice: unknown): choice is Separator;
}
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Separator = void 0;
const yoctocolors_cjs_1 = __importDefault(require("yoctocolors-cjs"));
const figures_1 = __importDefault(require("@inquirer/figures"));
/**
* Separator object
* Used to space/separate choices group
*/
class Separator {
separator = yoctocolors_cjs_1.default.dim(Array.from({ length: 15 }).join(figures_1.default.line));
type = 'separator';
constructor(separator) {
if (separator) {
this.separator = separator;
}
}
static isSeparator(choice) {
return Boolean(choice &&
typeof choice === 'object' &&
'type' in choice &&
choice.type === 'separator');
}
}
exports.Separator = Separator;
import type { Prettify } from '@inquirer/type';
/**
* Union type representing the possible statuses of a prompt.
*
* - `'loading'`: The prompt is currently loading.
* - `'idle'`: The prompt is loaded and currently waiting for the user to
* submit an answer.
* - `'done'`: The user has submitted an answer and the prompt is finished.
* - `string`: Any other string: The prompt is in a custom state.
*/
export type Status = 'loading' | 'idle' | 'done' | (string & {});
type DefaultTheme = {
/**
* Prefix to prepend to the message. If a function is provided, it will be
* called with the current status of the prompt, and the return value will be
* used as the prefix.
*
* @remarks
* If `status === 'loading'`, this property is ignored and the spinner (styled
* by the `spinner` property) will be displayed instead.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (status) => status === 'done' ? colors.green('✔') : colors.blue('?')
* ```
*/
prefix: string | Prettify<Omit<Record<Status, string>, 'loading'>>;
/**
* Configuration for the spinner that is displayed when the prompt is in the
* `'loading'` state.
*
* We recommend the use of {@link https://github.com/sindresorhus/cli-spinners|cli-spinners} for a list of available spinners.
*/
spinner: {
/**
* The time interval between frames, in milliseconds.
*
* @defaultValue
* ```ts
* 80
* ```
*/
interval: number;
/**
* A list of frames to show for the spinner.
*
* @defaultValue
* ```ts
* ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
* ```
*/
frames: string[];
};
/**
* Object containing functions to style different parts of the prompt.
*/
style: {
/**
* Style to apply to the user's answer once it has been submitted.
*
* @param text - The user's answer.
* @returns The styled answer.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.cyan(text)
* ```
*/
answer: (text: string) => string;
/**
* Style to apply to the message displayed to the user.
*
* @param text - The message to style.
* @param status - The current status of the prompt.
* @returns The styled message.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text, status) => colors.bold(text)
* ```
*/
message: (text: string, status: Status) => string;
/**
* Style to apply to error messages.
*
* @param text - The error message.
* @returns The styled error message.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.red(`> ${text}`)
* ```
*/
error: (text: string) => string;
/**
* Style to apply to the default answer when one is provided.
*
* @param text - The default answer.
* @returns The styled default answer.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.dim(`(${text})`)
* ```
*/
defaultAnswer: (text: string) => string;
/**
* Style to apply to help text.
*
* @param text - The help text.
* @returns The styled help text.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.dim(text)
* ```
*/
help: (text: string) => string;
/**
* Style to apply to highlighted text.
*
* @param text - The text to highlight.
* @returns The highlighted text.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.cyan(text)
* ```
*/
highlight: (text: string) => string;
/**
* Style to apply to keyboard keys referred to in help texts.
*
* @param text - The key to style.
* @returns The styled key.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.cyan(colors.bold(`<${text}>`))
* ```
*/
key: (text: string) => string;
};
};
export type Theme<Extension extends object = object> = Prettify<Extension & DefaultTheme>;
export declare const defaultTheme: DefaultTheme;
export {};
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultTheme = void 0;
const yoctocolors_cjs_1 = __importDefault(require("yoctocolors-cjs"));
const figures_1 = __importDefault(require("@inquirer/figures"));
exports.defaultTheme = {
prefix: {
idle: yoctocolors_cjs_1.default.blue('?'),
done: yoctocolors_cjs_1.default.green(figures_1.default.tick),
},
spinner: {
interval: 80,
frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'].map((frame) => yoctocolors_cjs_1.default.yellow(frame)),
},
style: {
answer: yoctocolors_cjs_1.default.cyan,
message: yoctocolors_cjs_1.default.bold,
error: (text) => yoctocolors_cjs_1.default.red(`> ${text}`),
defaultAnswer: (text) => yoctocolors_cjs_1.default.dim(`(${text})`),
help: yoctocolors_cjs_1.default.dim,
highlight: yoctocolors_cjs_1.default.cyan,
key: (text) => yoctocolors_cjs_1.default.cyan(yoctocolors_cjs_1.default.bold(`<${text}>`)),
},
};
import type { InquirerReadline } from '@inquirer/type';
export declare function useEffect(cb: (rl: InquirerReadline) => void | (() => void), depArray: ReadonlyArray<unknown>): void;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useEffect = useEffect;
const hook_engine_ts_1 = require("./hook-engine.js");
function useEffect(cb, depArray) {
(0, hook_engine_ts_1.withPointer)((pointer) => {
const oldDeps = pointer.get();
const hasChanged = !Array.isArray(oldDeps) || depArray.some((dep, i) => !Object.is(dep, oldDeps[i]));
if (hasChanged) {
hook_engine_ts_1.effectScheduler.queue(cb);
}
pointer.set(depArray);
});
}
import { type InquirerReadline } from '@inquirer/type';
import { type KeypressEvent } from './key.ts';
export declare function useKeypress(userHandler: (event: KeypressEvent, rl: InquirerReadline) => void | Promise<void>): void;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useKeypress = useKeypress;
const use_ref_ts_1 = require("./use-ref.js");
const use_effect_ts_1 = require("./use-effect.js");
const hook_engine_ts_1 = require("./hook-engine.js");
function useKeypress(userHandler) {
const signal = (0, use_ref_ts_1.useRef)(userHandler);
signal.current = userHandler;
(0, use_effect_ts_1.useEffect)((rl) => {
let ignore = false;
const handler = (0, hook_engine_ts_1.withUpdates)((_input, event) => {
if (ignore)
return;
void signal.current(event, rl);
});
rl.input.on('keypress', handler);
return () => {
ignore = true;
rl.input.removeListener('keypress', handler);
};
}, []);
}
export declare function useMemo<Value>(fn: () => Value, dependencies: ReadonlyArray<unknown>): Value;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useMemo = useMemo;
const hook_engine_ts_1 = require("./hook-engine.js");
function useMemo(fn, dependencies) {
return (0, hook_engine_ts_1.withPointer)((pointer) => {
const prev = pointer.get();
if (!prev ||
prev.dependencies.length !== dependencies.length ||
prev.dependencies.some((dep, i) => dep !== dependencies[i])) {
const value = fn();
pointer.set({ value, dependencies });
return value;
}
return prev.value;
});
}
import type { Theme, Status } from './theme.ts';
export declare function usePrefix({ status, theme, }: {
status?: Status;
theme?: Theme;
}): string;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.usePrefix = usePrefix;
const use_state_ts_1 = require("./use-state.js");
const use_effect_ts_1 = require("./use-effect.js");
const make_theme_ts_1 = require("./make-theme.js");
function usePrefix({ status = 'idle', theme, }) {
const [showLoader, setShowLoader] = (0, use_state_ts_1.useState)(false);
const [tick, setTick] = (0, use_state_ts_1.useState)(0);
const { prefix, spinner } = (0, make_theme_ts_1.makeTheme)(theme);
(0, use_effect_ts_1.useEffect)(() => {
if (status === 'loading') {
let tickInterval;
let inc = -1;
// Delay displaying spinner by 300ms, to avoid flickering
const delayTimeout = setTimeout(() => {
setShowLoader(true);
tickInterval = setInterval(() => {
inc = inc + 1;
setTick(inc % spinner.frames.length);
}, spinner.interval);
}, 300);
return () => {
clearTimeout(delayTimeout);
clearInterval(tickInterval);
};
}
else {
setShowLoader(false);
}
}, [status]);
if (showLoader) {
return spinner.frames[tick];
}
// There's a delay before we show the loader. So we want to ignore `loading` here, and pass idle instead.
const iconName = status === 'loading' ? 'idle' : status;
return typeof prefix === 'string' ? prefix : (prefix[iconName] ?? prefix['idle']);
}
export declare function useRef<Value>(val: Value): {
current: Value;
};
export declare function useRef<Value>(val?: Value): {
current: Value | undefined;
};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useRef = useRef;
const use_state_ts_1 = require("./use-state.js");
function useRef(val) {
return (0, use_state_ts_1.useState)({ current: val })[0];
}
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) => void];
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useState = useState;
const node_async_hooks_1 = require("node:async_hooks");
const hook_engine_ts_1 = require("./hook-engine.js");
function useState(defaultValue) {
return (0, hook_engine_ts_1.withPointer)((pointer) => {
const setState = node_async_hooks_1.AsyncResource.bind(function setState(newValue) {
// Noop if the value is still the same.
if (pointer.get() !== newValue) {
pointer.set(newValue);
// Trigger re-render
(0, hook_engine_ts_1.handleChange)();
}
});
if (pointer.initialized) {
return [pointer.get(), setState];
}
const value = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
pointer.set(value);
return [value, setState];
});
}
/**
* Force line returns at specific width. This function is ANSI code friendly and it'll
* ignore invisible codes during width calculation.
* @param {string} content
* @param {number} width
* @return {string}
*/
export declare function breakLines(content: string, width: number): string;
/**
* Returns the width of the active readline, or 80 as default value.
* @returns {number}
*/
export declare function readlineWidth(): number;
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.breakLines = breakLines;
exports.readlineWidth = readlineWidth;
const cli_width_1 = __importDefault(require("cli-width"));
const wrap_ansi_1 = __importDefault(require("wrap-ansi"));
const hook_engine_ts_1 = require("./hook-engine.js");
/**
* Force line returns at specific width. This function is ANSI code friendly and it'll
* ignore invisible codes during width calculation.
* @param {string} content
* @param {number} width
* @return {string}
*/
function breakLines(content, width) {
return content
.split('\n')
.flatMap((line) => (0, wrap_ansi_1.default)(line, width, { trim: false, hard: true })
.split('\n')
.map((str) => str.trimEnd()))
.join('\n');
}
/**
* Returns the width of the active readline, or 80 as default value.
* @returns {number}
*/
function readlineWidth() {
return (0, cli_width_1.default)({ defaultWidth: 80, output: (0, hook_engine_ts_1.readline)().output });
}
{
"type": "commonjs"
}
export { isUpKey, isDownKey, isSpaceKey, isBackspaceKey, isTabKey, isNumberKey, isEnterKey, type KeypressEvent, type Keybinding, } from './lib/key.ts';
export * from './lib/errors.ts';
export { usePrefix } from './lib/use-prefix.ts';
export { useState } from './lib/use-state.ts';
export { useEffect } from './lib/use-effect.ts';
export { useMemo } from './lib/use-memo.ts';
export { useRef } from './lib/use-ref.ts';
export { useKeypress } from './lib/use-keypress.ts';
export { makeTheme } from './lib/make-theme.ts';
export type { Theme, Status } from './lib/theme.ts';
export { usePagination } from './lib/pagination/use-pagination.ts';
export { createPrompt } from './lib/create-prompt.ts';
export { Separator } from './lib/Separator.ts';
export { isUpKey, isDownKey, isSpaceKey, isBackspaceKey, isTabKey, isNumberKey, isEnterKey, } from "./lib/key.js";
export * from "./lib/errors.js";
export { usePrefix } from "./lib/use-prefix.js";
export { useState } from "./lib/use-state.js";
export { useEffect } from "./lib/use-effect.js";
export { useMemo } from "./lib/use-memo.js";
export { useRef } from "./lib/use-ref.js";
export { useKeypress } from "./lib/use-keypress.js";
export { makeTheme } from "./lib/make-theme.js";
export { usePagination } from "./lib/pagination/use-pagination.js";
export { createPrompt } from "./lib/create-prompt.js";
export { Separator } from "./lib/Separator.js";
import { type Prompt, type Prettify } from '@inquirer/type';
type ViewFunction<Value, Config> = (config: Prettify<Config>, done: (value: Value) => void) => string | [string, string | undefined];
export declare function createPrompt<Value, Config>(view: ViewFunction<Value, Config>): Prompt<Value, Config>;
export {};
import * as readline from 'node:readline';
import { AsyncResource } from 'node:async_hooks';
import MuteStream from 'mute-stream';
import { onExit as onSignalExit } from 'signal-exit';
import ScreenManager from "./screen-manager.js";
import { PromisePolyfill } from "./promise-polyfill.js";
import { withHooks, effectScheduler } from "./hook-engine.js";
import { AbortPromptError, CancelPromptError, ExitPromptError } from "./errors.js";
function getCallSites() {
// eslint-disable-next-line @typescript-eslint/unbound-method
const _prepareStackTrace = Error.prepareStackTrace;
let result = [];
try {
Error.prepareStackTrace = (_, callSites) => {
const callSitesWithoutCurrent = callSites.slice(1);
result = callSitesWithoutCurrent;
return callSitesWithoutCurrent;
};
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
new Error().stack;
}
catch {
// An error will occur if the Node flag --frozen-intrinsics is used.
// https://nodejs.org/api/cli.html#--frozen-intrinsics
return result;
}
Error.prepareStackTrace = _prepareStackTrace;
return result;
}
export function createPrompt(view) {
const callSites = getCallSites();
const prompt = (config, context = {}) => {
// Default `input` to stdin
const { input = process.stdin, signal } = context;
const cleanups = new Set();
// Add mute capabilities to the output
const output = new MuteStream();
output.pipe(context.output ?? process.stdout);
const rl = readline.createInterface({
terminal: true,
input,
output,
});
const screen = new ScreenManager(rl);
const { promise, resolve, reject } = PromisePolyfill.withResolver();
const cancel = () => reject(new CancelPromptError());
if (signal) {
const abort = () => reject(new AbortPromptError({ cause: signal.reason }));
if (signal.aborted) {
abort();
return Object.assign(promise, { cancel });
}
signal.addEventListener('abort', abort);
cleanups.add(() => signal.removeEventListener('abort', abort));
}
cleanups.add(onSignalExit((code, signal) => {
reject(new ExitPromptError(`User force closed the prompt with ${code} ${signal}`));
}));
// SIGINT must be explicitly handled by the prompt so the ExitPromptError can be handled.
// Otherwise, the prompt will stop and in some scenarios never resolve.
// Ref issue #1741
const sigint = () => reject(new ExitPromptError(`User force closed the prompt with SIGINT`));
rl.on('SIGINT', sigint);
cleanups.add(() => rl.removeListener('SIGINT', sigint));
// Re-renders only happen when the state change; but the readline cursor could change position
// and that also requires a re-render (and a manual one because we mute the streams).
// We set the listener after the initial workLoop to avoid a double render if render triggered
// by a state change sets the cursor to the right position.
const checkCursorPos = () => screen.checkCursorPos();
rl.input.on('keypress', checkCursorPos);
cleanups.add(() => rl.input.removeListener('keypress', checkCursorPos));
return withHooks(rl, (cycle) => {
// The close event triggers immediately when the user press ctrl+c. SignalExit on the other hand
// triggers after the process is done (which happens after timeouts are done triggering.)
// We triggers the hooks cleanup phase on rl `close` so active timeouts can be cleared.
const hooksCleanup = AsyncResource.bind(() => effectScheduler.clearAll());
rl.on('close', hooksCleanup);
cleanups.add(() => rl.removeListener('close', hooksCleanup));
cycle(() => {
try {
const nextView = view(config, (value) => {
setImmediate(() => resolve(value));
});
// Typescript won't allow this, but not all users rely on typescript.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (nextView === undefined) {
const callerFilename = callSites[1]?.getFileName();
throw new Error(`Prompt functions must return a string.\n at ${callerFilename}`);
}
const [content, bottomContent] = typeof nextView === 'string' ? [nextView] : nextView;
screen.render(content, bottomContent);
effectScheduler.run();
}
catch (error) {
reject(error);
}
});
return Object.assign(promise
.then((answer) => {
effectScheduler.clearAll();
return answer;
}, (error) => {
effectScheduler.clearAll();
throw error;
})
// Wait for the promise to settle, then cleanup.
.finally(() => {
cleanups.forEach((cleanup) => cleanup());
screen.done({ clearContent: Boolean(context.clearPromptOnDone) });
output.end();
})
// Once cleanup is done, let the expose promise resolve/reject to the internal one.
.then(() => promise), { cancel });
});
};
return prompt;
}
export declare class AbortPromptError extends Error {
name: string;
message: string;
constructor(options?: {
cause?: unknown;
});
}
export declare class CancelPromptError extends Error {
name: string;
message: string;
}
export declare class ExitPromptError extends Error {
name: string;
}
export declare class HookError extends Error {
name: string;
}
export declare class ValidationError extends Error {
name: string;
}
export class AbortPromptError extends Error {
name = 'AbortPromptError';
message = 'Prompt was aborted';
constructor(options) {
super();
this.cause = options?.cause;
}
}
export class CancelPromptError extends Error {
name = 'CancelPromptError';
message = 'Prompt was canceled';
}
export class ExitPromptError extends Error {
name = 'ExitPromptError';
}
export class HookError extends Error {
name = 'HookError';
}
export class ValidationError extends Error {
name = 'ValidationError';
}
import type { InquirerReadline } from '@inquirer/type';
export declare function withHooks<T>(rl: InquirerReadline, cb: (cycle: (render: () => void) => void) => T): T;
export declare function readline(): InquirerReadline;
export declare function withUpdates<Args extends unknown[], R>(fn: (...args: Args) => R): (...args: Args) => R;
type SetPointer<Value> = {
get(): Value;
set(value: Value): void;
initialized: true;
};
type UnsetPointer<Value> = {
get(): void;
set(value: Value): void;
initialized: false;
};
type Pointer<Value> = SetPointer<Value> | UnsetPointer<Value>;
export declare function withPointer<Value, ReturnValue>(cb: (pointer: Pointer<Value>) => ReturnValue): ReturnValue;
export declare function handleChange(): void;
export declare const effectScheduler: {
queue(cb: (readline: InquirerReadline) => void | (() => void)): void;
run(): void;
clearAll(): void;
};
export {};
/* eslint @typescript-eslint/no-explicit-any: ["off"] */
import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks';
import { HookError, ValidationError } from "./errors.js";
const hookStorage = new AsyncLocalStorage();
function createStore(rl) {
const store = {
rl,
hooks: [],
hooksCleanup: [],
hooksEffect: [],
index: 0,
handleChange() { },
};
return store;
}
// Run callback in with the hook engine setup.
export function withHooks(rl, cb) {
const store = createStore(rl);
return hookStorage.run(store, () => {
function cycle(render) {
store.handleChange = () => {
store.index = 0;
render();
};
store.handleChange();
}
return cb(cycle);
});
}
// Safe getStore utility that'll return the store or throw if undefined.
function getStore() {
const store = hookStorage.getStore();
if (!store) {
throw new HookError('[Inquirer] Hook functions can only be called from within a prompt');
}
return store;
}
export function readline() {
return getStore().rl;
}
// Merge state updates happening within the callback function to avoid multiple renders.
export function withUpdates(fn) {
const wrapped = (...args) => {
const store = getStore();
let shouldUpdate = false;
const oldHandleChange = store.handleChange;
store.handleChange = () => {
shouldUpdate = true;
};
const returnValue = fn(...args);
if (shouldUpdate) {
oldHandleChange();
}
store.handleChange = oldHandleChange;
return returnValue;
};
return AsyncResource.bind(wrapped);
}
export function withPointer(cb) {
const store = getStore();
const { index } = store;
const pointer = {
get() {
return store.hooks[index];
},
set(value) {
store.hooks[index] = value;
},
initialized: index in store.hooks,
};
const returnValue = cb(pointer);
store.index++;
return returnValue;
}
export function handleChange() {
getStore().handleChange();
}
export const effectScheduler = {
queue(cb) {
const store = getStore();
const { index } = store;
store.hooksEffect.push(() => {
store.hooksCleanup[index]?.();
const cleanFn = cb(readline());
if (cleanFn != null && typeof cleanFn !== 'function') {
throw new ValidationError('useEffect return value must be a cleanup function or nothing.');
}
store.hooksCleanup[index] = cleanFn;
});
},
run() {
const store = getStore();
withUpdates(() => {
store.hooksEffect.forEach((effect) => {
effect();
});
// Warning: Clean the hooks before exiting the `withUpdates` block.
// Failure to do so means an updates would hit the same effects again.
store.hooksEffect.length = 0;
})();
},
clearAll() {
const store = getStore();
store.hooksCleanup.forEach((cleanFn) => {
cleanFn?.();
});
store.hooksEffect.length = 0;
store.hooksCleanup.length = 0;
},
};
export type KeypressEvent = {
name: string;
ctrl: boolean;
};
export type Keybinding = 'emacs' | 'vim';
export declare const isUpKey: (key: KeypressEvent, keybindings?: ReadonlyArray<Keybinding>) => boolean;
export declare const isDownKey: (key: KeypressEvent, keybindings?: ReadonlyArray<Keybinding>) => boolean;
export declare const isSpaceKey: (key: KeypressEvent) => boolean;
export declare const isBackspaceKey: (key: KeypressEvent) => boolean;
export declare const isTabKey: (key: KeypressEvent) => boolean;
export declare const isNumberKey: (key: KeypressEvent) => boolean;
export declare const isEnterKey: (key: KeypressEvent) => boolean;
export const isUpKey = (key, keybindings = []) =>
// The up key
key.name === 'up' ||
// Vim keybinding: hjkl keys map to left/down/up/right
(keybindings.includes('vim') && key.name === 'k') ||
// Emacs keybinding: Ctrl+P means "previous" in Emacs navigation conventions
(keybindings.includes('emacs') && key.ctrl && key.name === 'p');
export const isDownKey = (key, keybindings = []) =>
// The down key
key.name === 'down' ||
// Vim keybinding: hjkl keys map to left/down/up/right
(keybindings.includes('vim') && key.name === 'j') ||
// Emacs keybinding: Ctrl+N means "next" in Emacs navigation conventions
(keybindings.includes('emacs') && key.ctrl && key.name === 'n');
export const isSpaceKey = (key) => key.name === 'space';
export const isBackspaceKey = (key) => key.name === 'backspace';
export const isTabKey = (key) => key.name === 'tab';
export const isNumberKey = (key) => '1234567890'.includes(key.name);
export const isEnterKey = (key) => key.name === 'enter' || key.name === 'return';
import type { Prettify, PartialDeep } from '@inquirer/type';
import { type Theme } from './theme.ts';
export declare function makeTheme<SpecificTheme extends object>(...themes: ReadonlyArray<undefined | PartialDeep<Theme<SpecificTheme>>>): Prettify<Theme<SpecificTheme>>;
import { defaultTheme } from "./theme.js";
function isPlainObject(value) {
if (typeof value !== 'object' || value === null)
return false;
let proto = value;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(value) === proto;
}
function deepMerge(...objects) {
const output = {};
for (const obj of objects) {
for (const [key, value] of Object.entries(obj)) {
const prevValue = output[key];
output[key] =
isPlainObject(prevValue) && isPlainObject(value)
? deepMerge(prevValue, value)
: value;
}
}
return output;
}
export function makeTheme(...themes) {
const themesToMerge = [
defaultTheme,
...themes.filter((theme) => theme != null),
];
return deepMerge(...themesToMerge);
}
import type { Prettify } from '@inquirer/type';
export declare function usePagination<T>({ items, active, renderItem, pageSize, loop, }: {
items: ReadonlyArray<T>;
/** The index of the active item. */
active: number;
/** Renders an item as part of a page. */
renderItem: (layout: Prettify<{
item: T;
index: number;
isActive: boolean;
}>) => string;
/** The size of the page. */
pageSize: number;
/** Allows creating an infinitely looping list. `true` if unspecified. */
loop?: boolean;
}): string;
import { useRef } from "../use-ref.js";
import { readlineWidth, breakLines } from "../utils.js";
function usePointerPosition({ active, renderedItems, pageSize, loop, }) {
const state = useRef({
lastPointer: active,
lastActive: undefined,
});
const { lastPointer, lastActive } = state.current;
const middle = Math.floor(pageSize / 2);
const renderedLength = renderedItems.reduce((acc, item) => acc + item.length, 0);
const defaultPointerPosition = renderedItems
.slice(0, active)
.reduce((acc, item) => acc + item.length, 0);
let pointer = defaultPointerPosition;
if (renderedLength > pageSize) {
if (loop) {
/**
* Creates the next position for the pointer considering an infinitely
* looping list of items to be rendered on the page.
*
* The goal is to progressively move the cursor to the middle position as the user move down, and then keep
* the cursor there. When the user move up, maintain the cursor position.
*/
// By default, keep the cursor position as-is.
pointer = lastPointer;
if (
// First render, skip this logic.
lastActive != null &&
// Only move the pointer down when the user moves down.
lastActive < active &&
// Check user didn't move up across page boundary.
active - lastActive < pageSize) {
pointer = Math.min(
// Furthest allowed position for the pointer is the middle of the list
middle, Math.abs(active - lastActive) === 1
? Math.min(
// Move the pointer at most the height of the last active item.
lastPointer + (renderedItems[lastActive]?.length ?? 0),
// If the user moved by one item, move the pointer to the natural position of the active item as
// long as it doesn't move the cursor up.
Math.max(defaultPointerPosition, lastPointer))
: // Otherwise, move the pointer down by the difference between the active and last active item.
lastPointer + active - lastActive);
}
}
else {
/**
* Creates the next position for the pointer considering a finite list of
* items to be rendered on a page.
*
* The goal is to keep the pointer in the middle of the page whenever possible, until
* we reach the bounds of the list (top or bottom). In which case, the cursor moves progressively
* to the bottom or top of the list.
*/
const spaceUnderActive = renderedItems
.slice(active)
.reduce((acc, item) => acc + item.length, 0);
pointer =
spaceUnderActive < pageSize - middle
? // If the active item is near the end of the list, progressively move the cursor towards the end.
pageSize - spaceUnderActive
: // Otherwise, progressively move the pointer to the middle of the list.
Math.min(defaultPointerPosition, middle);
}
}
// Save state for the next render
state.current.lastPointer = pointer;
state.current.lastActive = active;
return pointer;
}
export function usePagination({ items, active, renderItem, pageSize, loop = true, }) {
const width = readlineWidth();
const bound = (num) => ((num % items.length) + items.length) % items.length;
const renderedItems = items.map((item, index) => {
if (item == null)
return [];
return breakLines(renderItem({ item, index, isActive: index === active }), width).split('\n');
});
const renderedLength = renderedItems.reduce((acc, item) => acc + item.length, 0);
const renderItemAtIndex = (index) => renderedItems[index] ?? [];
const pointer = usePointerPosition({ active, renderedItems, pageSize, loop });
// Render the active item to decide the position.
// If the active item fits under the pointer, we render it there.
// Otherwise, we need to render it to fit at the bottom of the page; moving the pointer up.
const activeItem = renderItemAtIndex(active).slice(0, pageSize);
const activeItemPosition = pointer + activeItem.length <= pageSize ? pointer : pageSize - activeItem.length;
// Create an array of lines for the page, and add the lines of the active item into the page
const pageBuffer = Array.from({ length: pageSize });
pageBuffer.splice(activeItemPosition, activeItem.length, ...activeItem);
// Store to prevent rendering the same item twice
const itemVisited = new Set([active]);
// Fill the page under the active item
let bufferPointer = activeItemPosition + activeItem.length;
let itemPointer = bound(active + 1);
while (bufferPointer < pageSize &&
!itemVisited.has(itemPointer) &&
(loop && renderedLength > pageSize ? itemPointer !== active : itemPointer > active)) {
const lines = renderItemAtIndex(itemPointer);
const linesToAdd = lines.slice(0, pageSize - bufferPointer);
pageBuffer.splice(bufferPointer, linesToAdd.length, ...linesToAdd);
// Move pointers for next iteration
itemVisited.add(itemPointer);
bufferPointer += linesToAdd.length;
itemPointer = bound(itemPointer + 1);
}
// Fill the page over the active item
bufferPointer = activeItemPosition - 1;
itemPointer = bound(active - 1);
while (bufferPointer >= 0 &&
!itemVisited.has(itemPointer) &&
(loop && renderedLength > pageSize ? itemPointer !== active : itemPointer < active)) {
const lines = renderItemAtIndex(itemPointer);
const linesToAdd = lines.slice(Math.max(0, lines.length - bufferPointer - 1));
pageBuffer.splice(bufferPointer - linesToAdd.length + 1, linesToAdd.length, ...linesToAdd);
// Move pointers for next iteration
itemVisited.add(itemPointer);
bufferPointer -= linesToAdd.length;
itemPointer = bound(itemPointer - 1);
}
return pageBuffer.filter((line) => typeof line === 'string').join('\n');
}
export declare class PromisePolyfill<T> extends Promise<T> {
static withResolver<T>(): {
promise: Promise<T>;
resolve: (value: T) => void;
reject: (error: unknown) => void;
};
}
// TODO: Remove this class once Node 22 becomes the minimum supported version.
export class PromisePolyfill extends Promise {
// Available starting from Node 22
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers
static withResolver() {
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve: resolve, reject: reject };
}
}
import type { InquirerReadline } from '@inquirer/type';
export default class ScreenManager {
private height;
private extraLinesUnderPrompt;
private cursorPos;
private readonly rl;
constructor(rl: InquirerReadline);
write(content: string): void;
render(content: string, bottomContent?: string): void;
checkCursorPos(): void;
done({ clearContent }: {
clearContent: boolean;
}): void;
}
import { stripVTControlCharacters } from 'node:util';
import { breakLines, readlineWidth } from "./utils.js";
import { cursorDown, cursorUp, cursorTo, cursorShow, eraseLines } from '@inquirer/ansi';
const height = (content) => content.split('\n').length;
const lastLine = (content) => content.split('\n').pop() ?? '';
export default class ScreenManager {
// These variables are keeping information to allow correct prompt re-rendering
height = 0;
extraLinesUnderPrompt = 0;
cursorPos;
rl;
constructor(rl) {
this.rl = rl;
this.cursorPos = rl.getCursorPos();
}
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
const promptLine = lastLine(content);
const rawPromptLine = stripVTControlCharacters(promptLine);
// Remove the rl.line from our prompt. We can't rely on the content of
// rl.line (mainly because of the password prompt), so just rely on it's
// length.
let prompt = rawPromptLine;
if (this.rl.line.length > 0) {
prompt = prompt.slice(0, -this.rl.line.length);
}
this.rl.setPrompt(prompt);
// SetPrompt will change cursor position, now we can get correct value
this.cursorPos = this.rl.getCursorPos();
const width = readlineWidth();
content = breakLines(content, width);
bottomContent = breakLines(bottomContent, width);
// Manually insert an extra line if we're at the end of the line.
// This prevent the cursor from appearing at the beginning of the
// current line.
if (rawPromptLine.length % width === 0) {
content += '\n';
}
let output = content + (bottomContent ? '\n' + bottomContent : '');
/**
* Re-adjust the cursor at the correct position.
*/
// We need to consider parts of the prompt under the cursor as part of the bottom
// content in order to correctly cleanup and re-render.
const promptLineUpDiff = Math.floor(rawPromptLine.length / width) - this.cursorPos.rows;
const bottomContentHeight = promptLineUpDiff + (bottomContent ? height(bottomContent) : 0);
// Return cursor to the input position (on top of the bottomContent)
if (bottomContentHeight > 0)
output += cursorUp(bottomContentHeight);
// Return cursor to the initial left offset.
output += cursorTo(this.cursorPos.cols);
/**
* Render and store state for future re-rendering
*/
this.write(cursorDown(this.extraLinesUnderPrompt) + eraseLines(this.height) + output);
this.extraLinesUnderPrompt = bottomContentHeight;
this.height = height(output);
}
checkCursorPos() {
const cursorPos = this.rl.getCursorPos();
if (cursorPos.cols !== this.cursorPos.cols) {
this.write(cursorTo(cursorPos.cols));
this.cursorPos = cursorPos;
}
}
done({ clearContent }) {
this.rl.setPrompt('');
let output = cursorDown(this.extraLinesUnderPrompt);
output += clearContent ? eraseLines(this.height) : '\n';
output += cursorShow;
this.write(output);
this.rl.close();
}
}
/**
* Separator object
* Used to space/separate choices group
*/
export declare class Separator {
readonly separator: string;
readonly type: string;
constructor(separator?: string);
static isSeparator(choice: unknown): choice is Separator;
}
import colors from 'yoctocolors-cjs';
import figures from '@inquirer/figures';
/**
* Separator object
* Used to space/separate choices group
*/
export class Separator {
separator = colors.dim(Array.from({ length: 15 }).join(figures.line));
type = 'separator';
constructor(separator) {
if (separator) {
this.separator = separator;
}
}
static isSeparator(choice) {
return Boolean(choice &&
typeof choice === 'object' &&
'type' in choice &&
choice.type === 'separator');
}
}
import type { Prettify } from '@inquirer/type';
/**
* Union type representing the possible statuses of a prompt.
*
* - `'loading'`: The prompt is currently loading.
* - `'idle'`: The prompt is loaded and currently waiting for the user to
* submit an answer.
* - `'done'`: The user has submitted an answer and the prompt is finished.
* - `string`: Any other string: The prompt is in a custom state.
*/
export type Status = 'loading' | 'idle' | 'done' | (string & {});
type DefaultTheme = {
/**
* Prefix to prepend to the message. If a function is provided, it will be
* called with the current status of the prompt, and the return value will be
* used as the prefix.
*
* @remarks
* If `status === 'loading'`, this property is ignored and the spinner (styled
* by the `spinner` property) will be displayed instead.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (status) => status === 'done' ? colors.green('✔') : colors.blue('?')
* ```
*/
prefix: string | Prettify<Omit<Record<Status, string>, 'loading'>>;
/**
* Configuration for the spinner that is displayed when the prompt is in the
* `'loading'` state.
*
* We recommend the use of {@link https://github.com/sindresorhus/cli-spinners|cli-spinners} for a list of available spinners.
*/
spinner: {
/**
* The time interval between frames, in milliseconds.
*
* @defaultValue
* ```ts
* 80
* ```
*/
interval: number;
/**
* A list of frames to show for the spinner.
*
* @defaultValue
* ```ts
* ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
* ```
*/
frames: string[];
};
/**
* Object containing functions to style different parts of the prompt.
*/
style: {
/**
* Style to apply to the user's answer once it has been submitted.
*
* @param text - The user's answer.
* @returns The styled answer.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.cyan(text)
* ```
*/
answer: (text: string) => string;
/**
* Style to apply to the message displayed to the user.
*
* @param text - The message to style.
* @param status - The current status of the prompt.
* @returns The styled message.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text, status) => colors.bold(text)
* ```
*/
message: (text: string, status: Status) => string;
/**
* Style to apply to error messages.
*
* @param text - The error message.
* @returns The styled error message.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.red(`> ${text}`)
* ```
*/
error: (text: string) => string;
/**
* Style to apply to the default answer when one is provided.
*
* @param text - The default answer.
* @returns The styled default answer.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.dim(`(${text})`)
* ```
*/
defaultAnswer: (text: string) => string;
/**
* Style to apply to help text.
*
* @param text - The help text.
* @returns The styled help text.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.dim(text)
* ```
*/
help: (text: string) => string;
/**
* Style to apply to highlighted text.
*
* @param text - The text to highlight.
* @returns The highlighted text.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.cyan(text)
* ```
*/
highlight: (text: string) => string;
/**
* Style to apply to keyboard keys referred to in help texts.
*
* @param text - The key to style.
* @returns The styled key.
*
* @defaultValue
* ```ts
* // import colors from 'yoctocolors-cjs';
* (text) => colors.cyan(colors.bold(`<${text}>`))
* ```
*/
key: (text: string) => string;
};
};
export type Theme<Extension extends object = object> = Prettify<Extension & DefaultTheme>;
export declare const defaultTheme: DefaultTheme;
export {};
import colors from 'yoctocolors-cjs';
import figures from '@inquirer/figures';
export const defaultTheme = {
prefix: {
idle: colors.blue('?'),
done: colors.green(figures.tick),
},
spinner: {
interval: 80,
frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'].map((frame) => colors.yellow(frame)),
},
style: {
answer: colors.cyan,
message: colors.bold,
error: (text) => colors.red(`> ${text}`),
defaultAnswer: (text) => colors.dim(`(${text})`),
help: colors.dim,
highlight: colors.cyan,
key: (text) => colors.cyan(colors.bold(`<${text}>`)),
},
};
import type { InquirerReadline } from '@inquirer/type';
export declare function useEffect(cb: (rl: InquirerReadline) => void | (() => void), depArray: ReadonlyArray<unknown>): void;
import { withPointer, effectScheduler } from "./hook-engine.js";
export function useEffect(cb, depArray) {
withPointer((pointer) => {
const oldDeps = pointer.get();
const hasChanged = !Array.isArray(oldDeps) || depArray.some((dep, i) => !Object.is(dep, oldDeps[i]));
if (hasChanged) {
effectScheduler.queue(cb);
}
pointer.set(depArray);
});
}
import { type InquirerReadline } from '@inquirer/type';
import { type KeypressEvent } from './key.ts';
export declare function useKeypress(userHandler: (event: KeypressEvent, rl: InquirerReadline) => void | Promise<void>): void;
import { useRef } from "./use-ref.js";
import { useEffect } from "./use-effect.js";
import { withUpdates } from "./hook-engine.js";
export function useKeypress(userHandler) {
const signal = useRef(userHandler);
signal.current = userHandler;
useEffect((rl) => {
let ignore = false;
const handler = withUpdates((_input, event) => {
if (ignore)
return;
void signal.current(event, rl);
});
rl.input.on('keypress', handler);
return () => {
ignore = true;
rl.input.removeListener('keypress', handler);
};
}, []);
}
export declare function useMemo<Value>(fn: () => Value, dependencies: ReadonlyArray<unknown>): Value;
import { withPointer } from "./hook-engine.js";
export function useMemo(fn, dependencies) {
return withPointer((pointer) => {
const prev = pointer.get();
if (!prev ||
prev.dependencies.length !== dependencies.length ||
prev.dependencies.some((dep, i) => dep !== dependencies[i])) {
const value = fn();
pointer.set({ value, dependencies });
return value;
}
return prev.value;
});
}
import type { Theme, Status } from './theme.ts';
export declare function usePrefix({ status, theme, }: {
status?: Status;
theme?: Theme;
}): string;
import { useState } from "./use-state.js";
import { useEffect } from "./use-effect.js";
import { makeTheme } from "./make-theme.js";
export function usePrefix({ status = 'idle', theme, }) {
const [showLoader, setShowLoader] = useState(false);
const [tick, setTick] = useState(0);
const { prefix, spinner } = makeTheme(theme);
useEffect(() => {
if (status === 'loading') {
let tickInterval;
let inc = -1;
// Delay displaying spinner by 300ms, to avoid flickering
const delayTimeout = setTimeout(() => {
setShowLoader(true);
tickInterval = setInterval(() => {
inc = inc + 1;
setTick(inc % spinner.frames.length);
}, spinner.interval);
}, 300);
return () => {
clearTimeout(delayTimeout);
clearInterval(tickInterval);
};
}
else {
setShowLoader(false);
}
}, [status]);
if (showLoader) {
return spinner.frames[tick];
}
// There's a delay before we show the loader. So we want to ignore `loading` here, and pass idle instead.
const iconName = status === 'loading' ? 'idle' : status;
return typeof prefix === 'string' ? prefix : (prefix[iconName] ?? prefix['idle']);
}
export declare function useRef<Value>(val: Value): {
current: Value;
};
export declare function useRef<Value>(val?: Value): {
current: Value | undefined;
};
import { useState } from "./use-state.js";
export function useRef(val) {
return useState({ current: val })[0];
}
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) => void];
export {};
import { AsyncResource } from 'node:async_hooks';
import { withPointer, handleChange } from "./hook-engine.js";
export function useState(defaultValue) {
return withPointer((pointer) => {
const setState = AsyncResource.bind(function setState(newValue) {
// Noop if the value is still the same.
if (pointer.get() !== newValue) {
pointer.set(newValue);
// Trigger re-render
handleChange();
}
});
if (pointer.initialized) {
return [pointer.get(), setState];
}
const value = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
pointer.set(value);
return [value, setState];
});
}
/**
* Force line returns at specific width. This function is ANSI code friendly and it'll
* ignore invisible codes during width calculation.
* @param {string} content
* @param {number} width
* @return {string}
*/
export declare function breakLines(content: string, width: number): string;
/**
* Returns the width of the active readline, or 80 as default value.
* @returns {number}
*/
export declare function readlineWidth(): number;
import cliWidth from 'cli-width';
import wrapAnsi from 'wrap-ansi';
import { readline } from "./hook-engine.js";
/**
* Force line returns at specific width. This function is ANSI code friendly and it'll
* ignore invisible codes during width calculation.
* @param {string} content
* @param {number} width
* @return {string}
*/
export function breakLines(content, width) {
return content
.split('\n')
.flatMap((line) => wrapAnsi(line, width, { trim: false, hard: true })
.split('\n')
.map((str) => str.trimEnd()))
.join('\n');
}
/**
* Returns the width of the active readline, or 80 as default value.
* @returns {number}
*/
export function readlineWidth() {
return cliWidth({ defaultWidth: 80, output: readline().output });
}
{
"type": "module"
}