This commit is contained in:
CHEVALLIER Abel
2025-11-13 16:23:22 +01:00
parent de9c515a47
commit cb235644dc
34924 changed files with 3811102 additions and 0 deletions

21
node_modules/@angular/cli/LICENSE generated vendored Executable file
View File

@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2010-2025 Google LLC. https://angular.dev/license
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

5
node_modules/@angular/cli/README.md generated vendored Executable file
View File

@@ -0,0 +1,5 @@
# Angular CLI - The CLI tool for Angular.
The sources for this package are in the [Angular CLI](https://github.com/angular/angular-cli) repository. Please file issues and pull requests against that repository.
Usage information and reference details can be found in repository [README](https://github.com/angular/angular-cli/blob/main/README.md) file.

33
node_modules/@angular/cli/bin/bootstrap.js generated vendored Executable file
View File

@@ -0,0 +1,33 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
/**
* @fileoverview
*
* This file is used to bootstrap the CLI process by dynamically importing the main initialization code.
* This is done to allow the main bin file (`ng`) to remain CommonJS so that older versions of Node.js
* can be checked and validated prior to the execution of the CLI. This separate bootstrap file is
* needed to allow the use of a dynamic import expression without crashing older versions of Node.js that
* do not support dynamic import expressions and would otherwise throw a syntax error. This bootstrap file
* is required from the main bin file only after the Node.js version is determined to be in the supported
* range.
*/
// Enable on-disk code caching if available (Node.js 22.8+)
// Skip if running inside Bazel via a RUNFILES environment variable check and no explicit cache
// location defined. The default cache location does not work well with Bazel's hermeticity requirements.
if (!process.env['RUNFILES'] || process.env['NODE_COMPILE_CACHE']) {
try {
const { enableCompileCache } = require('node:module');
enableCompileCache?.();
} catch {}
}
// Initialize the Angular CLI
void import('../lib/init.js');

71
node_modules/@angular/cli/bin/ng.js generated vendored Executable file
View File

@@ -0,0 +1,71 @@
#!/usr/bin/env node
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
/* eslint-disable no-console */
/* eslint-disable import/no-unassigned-import */
'use strict';
const path = require('path');
// Error if the external CLI appears to be used inside a google3 context.
if (process.cwd().split(path.sep).includes('google3')) {
console.error(
'This is the external Angular CLI, but you appear to be running in google3. There is a separate, internal version of the CLI which should be used instead. See http://go/angular/cli.',
);
process.exit();
}
// Provide a title to the process in `ps`.
// Due to an obscure Mac bug, do not start this title with any symbol.
try {
process.title = 'ng ' + Array.from(process.argv).slice(2).join(' ');
} catch (_) {
// If an error happened above, use the most basic title.
process.title = 'ng';
}
const rawCommandName = process.argv[2];
if (rawCommandName === '--get-yargs-completions' || rawCommandName === 'completion') {
// Skip Node.js supported checks when running ng completion.
// A warning at this stage could cause a broken source action (`source <(ng completion script)`) when in the shell init script.
require('./bootstrap');
return;
}
// This node version check ensures that extremely old versions of node are not used.
// These may not support ES2015 features such as const/let/async/await/etc.
// These would then crash with a hard to diagnose error message.
const [major, minor] = process.versions.node.split('.', 2).map((part) => Number(part));
if (major % 2 === 1) {
// Allow new odd numbered releases with a warning (currently v17+)
console.warn(
'Node.js version ' +
process.version +
' detected.\n' +
'Odd numbered Node.js versions will not enter LTS status and should not be used for production.' +
' For more information, please see https://nodejs.org/en/about/previous-releases/.',
);
require('./bootstrap');
} else if (major < 20 || (major === 20 && minor < 19) || (major === 22 && minor < 12)) {
// Error and exit if less than 20.19 or 22.12
console.error(
'Node.js version ' +
process.version +
' detected.\n' +
'The Angular CLI requires a minimum Node.js version of v20.19 or v22.12.\n\n' +
'Please update your Node.js version or visit https://nodejs.org/ for additional instructions.\n',
);
process.exitCode = 3;
} else {
require('./bootstrap');
}

3
node_modules/@angular/cli/bin/package.json generated vendored Executable file
View File

@@ -0,0 +1,3 @@
{
"type": "commonjs"
}

11
node_modules/@angular/cli/lib/cli/index.d.ts generated vendored Executable file
View File

@@ -0,0 +1,11 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
export { VERSION } from '../../src/utilities/version';
export default function (options: {
cliArgs: string[];
}): Promise<number>;

108
node_modules/@angular/cli/lib/cli/index.js generated vendored Executable file
View File

@@ -0,0 +1,108 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.VERSION = void 0;
exports.default = default_1;
const core_1 = require("@angular-devkit/core");
const node_util_1 = require("node:util");
const command_module_1 = require("../../src/command-builder/command-module");
const command_runner_1 = require("../../src/command-builder/command-runner");
const color_1 = require("../../src/utilities/color");
const environment_options_1 = require("../../src/utilities/environment-options");
const log_file_1 = require("../../src/utilities/log-file");
var version_1 = require("../../src/utilities/version");
Object.defineProperty(exports, "VERSION", { enumerable: true, get: function () { return version_1.VERSION; } });
const MIN_NODEJS_VERSION = [20, 19];
/* eslint-disable no-console */
async function default_1(options) {
// This node version check ensures that the requirements of the project instance of the CLI are met
const [major, minor] = process.versions.node.split('.').map((part) => Number(part));
if (major < MIN_NODEJS_VERSION[0] ||
(major === MIN_NODEJS_VERSION[0] && minor < MIN_NODEJS_VERSION[1])) {
process.stderr.write(`Node.js version ${process.version} detected.\n` +
`The Angular CLI requires a minimum of v${MIN_NODEJS_VERSION[0]}.${MIN_NODEJS_VERSION[1]}.\n\n` +
'Please update your Node.js version or visit https://nodejs.org/ for additional instructions.\n');
return 3;
}
const colorLevels = {
info: (s) => s,
debug: (s) => s,
warn: (s) => color_1.colors.bold(color_1.colors.yellow(s)),
error: (s) => color_1.colors.bold(color_1.colors.red(s)),
fatal: (s) => color_1.colors.bold(color_1.colors.red(s)),
};
const logger = new core_1.logging.IndentLogger('cli-main-logger');
const logInfo = console.log;
const logError = console.error;
const useColor = (0, color_1.supportColor)();
const loggerFinished = logger.forEach((entry) => {
if (!environment_options_1.ngDebug && entry.level === 'debug') {
return;
}
const color = useColor ? colorLevels[entry.level] : node_util_1.stripVTControlCharacters;
const message = color(entry.message);
switch (entry.level) {
case 'warn':
case 'fatal':
case 'error':
logError(message);
break;
default:
logInfo(message);
break;
}
});
// Redirect console to logger
console.info = console.log = function (...args) {
logger.info((0, node_util_1.format)(...args));
};
console.warn = function (...args) {
logger.warn((0, node_util_1.format)(...args));
};
console.error = function (...args) {
logger.error((0, node_util_1.format)(...args));
};
try {
return await (0, command_runner_1.runCommand)(options.cliArgs, logger);
}
catch (err) {
if (err instanceof command_module_1.CommandModuleError) {
logger.fatal(`Error: ${err.message}`);
}
else if (err instanceof Error) {
try {
const logPath = (0, log_file_1.writeErrorToLogFile)(err);
logger.fatal(`An unhandled exception occurred: ${err.message}\n` +
`See "${logPath}" for further details.`);
}
catch (e) {
logger.fatal(`An unhandled exception occurred: ${err.message}\n` +
`Fatal error writing debug log file: ${e}`);
if (err.stack) {
logger.fatal(err.stack);
}
}
return 127;
}
else if (typeof err === 'string') {
logger.fatal(err);
}
else if (typeof err === 'number') {
// Log nothing.
}
else {
logger.fatal(`An unexpected error occurred: ${err}`);
}
return 1;
}
finally {
logger.complete();
await loggerFinished;
}
}

BIN
node_modules/@angular/cli/lib/code-examples.db generated vendored Executable file

Binary file not shown.

5901
node_modules/@angular/cli/lib/config/schema.json generated vendored Executable file

File diff suppressed because it is too large Load Diff

966
node_modules/@angular/cli/lib/config/workspace-schema.d.ts generated vendored Executable file
View File

@@ -0,0 +1,966 @@
export type Schema = {
$schema?: string;
cli?: CliOptions;
/**
* Path where new projects will be created.
*/
newProjectRoot?: string;
projects?: Projects;
schematics?: SchematicOptions;
version: number;
};
export type CliOptions = {
/**
* Share pseudonymous usage data with the Angular Team at Google.
*/
analytics?: Analytics;
/**
* Control disk cache.
*/
cache?: Cache;
/**
* Specify which package manager tool to use.
*/
packageManager?: PackageManager;
/**
* The list of schematic collections to use.
*/
schematicCollections?: string[];
/**
* Control CLI specific console warnings
*/
warnings?: Warnings;
};
/**
* Share pseudonymous usage data with the Angular Team at Google.
*/
export type Analytics = boolean | string;
/**
* Control disk cache.
*/
export type Cache = {
/**
* Configure whether disk caching is enabled.
*/
enabled?: boolean;
/**
* Configure in which environment disk cache is enabled.
*/
environment?: Environment;
/**
* Cache base path.
*/
path?: string;
};
/**
* Configure in which environment disk cache is enabled.
*/
export declare enum Environment {
All = "all",
Ci = "ci",
Local = "local"
}
/**
* Specify which package manager tool to use.
*
* The package manager used to install dependencies.
*/
export declare enum PackageManager {
Bun = "bun",
Cnpm = "cnpm",
Npm = "npm",
Pnpm = "pnpm",
Yarn = "yarn"
}
/**
* Control CLI specific console warnings
*/
export type Warnings = {
/**
* Show a warning when the global version is newer than the local one.
*/
versionMismatch?: boolean;
};
export type Projects = {};
export type SchematicOptions = {
"@schematics/angular:application"?: AngularApplicationOptionsSchema;
"@schematics/angular:class"?: AngularClassOptionsSchema;
"@schematics/angular:component"?: AngularComponentOptionsSchema;
"@schematics/angular:directive"?: AngularDirectiveOptionsSchema;
"@schematics/angular:enum"?: AngularEnumOptionsSchema;
"@schematics/angular:guard"?: AngularGuardOptionsSchema;
"@schematics/angular:interceptor"?: AngularInterceptorOptionsSchema;
"@schematics/angular:interface"?: AngularInterfaceOptionsSchema;
"@schematics/angular:library"?: LibraryOptionsSchema;
"@schematics/angular:ng-new"?: AngularNgNewOptionsSchema;
"@schematics/angular:pipe"?: AngularPipeOptionsSchema;
"@schematics/angular:resolver"?: AngularResolverOptionsSchema;
"@schematics/angular:service"?: AngularServiceOptionsSchema;
"@schematics/angular:web-worker"?: AngularWebWorkerOptionsSchema;
[property: string]: any;
};
/**
* Generates a new Angular application within your workspace. This schematic sets up the
* foundational structure of your project, including the root component, module, and
* configuration files. You can customize various aspects of the application, such as
* routing, styling, and testing.
*/
export type AngularApplicationOptionsSchema = {
/**
* Include the styles for the root component directly within the `app.component.ts` file.
* Only CSS styles can be included inline. By default, a separate stylesheet file (e.g.,
* `app.component.css`) is created.
*/
inlineStyle?: boolean;
/**
* Include the HTML template for the root component directly within the `app.component.ts`
* file. By default, a separate template file (e.g., `app.component.html`) is created.
*/
inlineTemplate?: boolean;
/**
* Generate a minimal project without any testing frameworks. This is intended for learning
* purposes and simple experimentation, not for production applications.
*/
minimal?: boolean;
/**
* The name for the new application. This name will be used for the project directory and
* various identifiers throughout the application's code.
*/
name: string;
/**
* A prefix to be added to the selectors of components generated within this application.
* For example, if the prefix is `my-app` and you generate a component named `my-component`,
* the selector will be `my-app-my-component`.
*/
prefix?: string;
/**
* The directory where the new application's files will be created, relative to the
* workspace root. If not specified, the application will be created in a subfolder within
* the `projects` directory, using the application's name.
*/
projectRoot?: string;
/**
* Generate an application with routing already configured. This sets up the necessary files
* and modules for managing navigation between different views in your application.
*/
routing?: boolean;
/**
* Skip the automatic installation of packages. You will need to manually install the
* dependencies later.
*/
skipInstall?: boolean;
/**
* Do not add dependencies to the `package.json` file.
*/
skipPackageJson?: boolean;
/**
* Skip the generation of a unit test files `spec.ts`.
*/
skipTests?: boolean;
/**
* Configure the application for Server-Side Rendering (SSR) and Static Site Generation
* (SSG/Prerendering).
*/
ssr?: boolean;
/**
* Create an application that utilizes the standalone API, eliminating the need for
* NgModules. This can simplify the structure of your application.
*/
standalone?: boolean;
/**
* Enable stricter bundle budget settings for the application. This helps to keep your
* application's bundle size small and improve performance. For more information, see
* https://angular.dev/tools/cli/template-typecheck#strict-mode
*/
strict?: boolean;
/**
* The type of stylesheet files to be created for components in the application.
*/
style?: SchematicsAngularApplicationStyle;
/**
* Sets the view encapsulation mode for the application's components. This determines how
* component styles are scoped and applied.
*/
viewEncapsulation?: ViewEncapsulation;
/**
* Generate an application that does not use `zone.js`.
*/
zoneless?: boolean;
};
/**
* The type of stylesheet files to be created for components in the application.
*
* The type of stylesheet files to be created for components in the initial project.
*/
export declare enum SchematicsAngularApplicationStyle {
Css = "css",
Less = "less",
Sass = "sass",
Scss = "scss"
}
/**
* Sets the view encapsulation mode for the application's components. This determines how
* component styles are scoped and applied.
*
* Sets the view encapsulation mode for the component. This determines how the component's
* styles are scoped and applied.
*
* Sets the view encapsulation mode for components in the initial project. This determines
* how component styles are scoped and applied. Options include: `Emulated` (default, styles
* are scoped to the component), `None` (styles are global), and `ShadowDom` (styles are
* encapsulated using Shadow DOM).
*/
export declare enum ViewEncapsulation {
Emulated = "Emulated",
None = "None",
ShadowDom = "ShadowDom"
}
/**
* Creates a new class in your project. Classes are the fundamental building blocks for
* object-oriented programming in TypeScript. They provide a blueprint for creating objects
* with properties and methods. This schematic helps you generate a new class with the basic
* structure and optional test files.
*/
export type AngularClassOptionsSchema = {
/**
* The name for the new class. This will be used to create the class file (e.g.,
* `my-class.ts`) and, if enabled, the corresponding test file `my-class.spec.ts`.
*/
name: string;
/**
* The path where the class file should be created, relative to the workspace root. If not
* specified, the class will be created in the current directory.
*/
path?: string;
/**
* The name of the project where the class should be added. If not specified, the CLI will
* determine the project from the current directory.
*/
project: string;
/**
* Skip the generation of a unit test file `spec.ts` for the new class.
*/
skipTests?: boolean;
/**
* Adds a custom type to the filename, allowing you to create more descriptive class names.
* For example, if you set the type to `helper`, the filename will be `my-class.helper.ts`.
*/
type?: string;
};
/**
* Creates a new Angular component. Components are the basic building blocks of Angular
* applications. Each component consists of a TypeScript class, an HTML template, and an
* optional CSS stylesheet. Use this schematic to generate a new component in your project.
*/
export type AngularComponentOptionsSchema = {
/**
* Configures the change detection strategy for the component.
*/
changeDetection?: ChangeDetection;
/**
* Adds `:host { display: block; }` to the component's stylesheet, ensuring the component
* renders as a block-level element. This is useful for layout purposes.
*/
displayBlock?: boolean;
/**
* Automatically export the component from the specified NgModule, making it accessible to
* other modules in the application.
*/
export?: boolean;
/**
* Use a default export for the component in its TypeScript file instead of a named export.
*/
exportDefault?: boolean;
/**
* Create the component files directly in the project's `src/app` directory instead of
* creating a new folder for them.
*/
flat?: boolean;
/**
* Include the component's styles directly in the `component.ts` file. By default, a
* separate stylesheet file (e.g., `my-component.css`) is created.
*/
inlineStyle?: boolean;
/**
* Include the component's HTML template directly in the `component.ts` file. By default, a
* separate template file (e.g., `my-component.html`) is created.
*/
inlineTemplate?: boolean;
/**
* Specify the NgModule where the component should be declared. If not provided, the CLI
* will attempt to find the closest NgModule in the component's path.
*/
module?: string;
/**
* The name for the new component. This will be used to create the component's class,
* template, and stylesheet files. For example, if you provide `my-component`, the files
* will be named `my-component.ts`, `my-component.html`, and `my-component.css`.
*/
name: string;
/**
* Generate component template files with an '.ng.html' file extension instead of '.html'.
*/
ngHtml?: boolean;
/**
* The path where the component files should be created, relative to the current workspace.
* If not provided, a folder with the same name as the component will be created in the
* project's `src/app` directory.
*/
path?: string;
/**
* A prefix to be added to the component's selector. For example, if the prefix is `app` and
* the component name is `my-component`, the selector will be `app-my-component`.
*/
prefix?: string;
/**
* The name of the project where the component should be added. If not specified, the CLI
* will determine the project from the current directory.
*/
project: string;
/**
* The HTML selector to use for this component. If not provided, a selector will be
* generated based on the component name (e.g., `app-my-component`).
*/
selector?: string;
/**
* Do not automatically import the new component into its closest NgModule.
*/
skipImport?: boolean;
/**
* Skip the generation of an HTML selector for the component.
*/
skipSelector?: boolean;
/**
* Skip the generation of unit test files `spec.ts`.
*/
skipTests?: boolean;
/**
* Generate a standalone component. Standalone components are self-contained and don't need
* to be declared in an NgModule. They can be used independently or imported directly into
* other standalone components.
*/
standalone?: boolean;
/**
* Specify the type of stylesheet to be created for the component, or `none` to skip
* creating a stylesheet.
*/
style?: SchematicsAngularComponentStyle;
/**
* Append a custom type to the component's filename. For example, if you set the type to
* `container`, the file will be named `my-component.container.ts`.
*/
type?: string;
/**
* Sets the view encapsulation mode for the component. This determines how the component's
* styles are scoped and applied.
*/
viewEncapsulation?: ViewEncapsulation;
};
/**
* Configures the change detection strategy for the component.
*/
export declare enum ChangeDetection {
Default = "Default",
OnPush = "OnPush"
}
/**
* Specify the type of stylesheet to be created for the component, or `none` to skip
* creating a stylesheet.
*/
export declare enum SchematicsAngularComponentStyle {
Css = "css",
Less = "less",
None = "none",
Sass = "sass",
Scss = "scss"
}
/**
* Creates a new directive in your project. Directives are used to extend the behavior or
* appearance of HTML elements and components. They allow you to manipulate the DOM, add
* custom attributes, and respond to events. This schematic generates the necessary files
* and boilerplate code for a new directive.
*/
export type AngularDirectiveOptionsSchema = {
/**
* Automatically export the directive from the specified NgModule, making it accessible to
* other modules in the application.
*/
export?: boolean;
/**
* Creates the new directive files at the top level of the current project. If set to false,
* a new folder with the directive's name will be created to contain the files.
*/
flat?: boolean;
/**
* Specify the NgModule where the directive should be declared. If not provided, the CLI
* will attempt to find the closest NgModule in the directive's path.
*/
module?: string;
/**
* The name for the new directive. This will be used to create the directive's class and
* spec files (e.g., `my-directive.directive.ts` and `my-directive.directive.spec.ts`).
*/
name: string;
/**
* The path where the directive files should be created, relative to the workspace root. If
* not provided, the directive will be created in the current directory.
*/
path?: string;
/**
* A prefix to be added to the directive's selector. For example, if the prefix is `app` and
* the directive name is `highlight`, the selector will be `appHighlight`.
*/
prefix?: string;
/**
* The name of the project where the directive should be added. If not specified, the CLI
* will determine the project from the current directory.
*/
project: string;
/**
* The HTML selector to use for this directive. If not provided, a selector will be
* generated based on the directive's name (e.g., `appHighlight`).
*/
selector?: string;
/**
* Do not automatically import the new directive into its closest NgModule.
*/
skipImport?: boolean;
/**
* Skip the generation of a unit test file `spec.ts` for the new directive.
*/
skipTests?: boolean;
/**
* Generate a standalone directive. Standalone directives are self-contained and don't need
* to be declared in an NgModule. They can be used independently or imported directly into
* other standalone components or directives.
*/
standalone?: boolean;
/**
* Append a custom type to the directive's filename. For example, if you set the type to
* `directive`, the file will be named `example.directive.ts`.
*/
type?: string;
};
/**
* Creates a new enum in your project. Enums (enumerations) are a way to define a set of
* named constants, making your code more readable and maintainable. This schematic
* generates a new enum with the specified name and type.
*/
export type AngularEnumOptionsSchema = {
/**
* The name for the new enum. This will be used to create the enum file (e.g.,
* `my-enum.enum.ts`).
*/
name: string;
/**
* The path where the enum file should be created, relative to the current workspace. If not
* specified, the enum will be created in the current directory.
*/
path?: string;
/**
* The name of the project where the enum should be created. If not specified, the CLI will
* determine the project from the current directory.
*/
project: string;
/**
* Adds a custom type to the filename, allowing you to create more descriptive enum names.
* For example, if you set the type to `status`, the filename will be `my-enum.status.ts`.
*/
type?: string;
};
/**
* Creates a new route guard in your project. Route guards are used to control access to
* parts of your application by checking certain conditions before a route is activated.
* This schematic generates a new guard with the specified name, type, and options.
*/
export type AngularGuardOptionsSchema = {
/**
* Creates the new guard files at the top level of the current project. If set to false, a
* new folder with the guard's name will be created to contain the files.
*/
flat?: boolean;
/**
* Generate the guard as a function instead of a class. Functional guards can be simpler for
* basic scenarios.
*/
functional?: boolean;
/**
* Specifies the type(s) of guard to create. You can choose one or more of the following:
* `CanActivate` (controls access to a route), `CanActivateChild` (controls access to child
* routes), `CanDeactivate` (asks for confirmation before leaving a route), `CanMatch`
* (determines if a route can be matched).
*/
implements?: Implement[];
/**
* The name for the new route guard. This will be used to create the guard's class and spec
* files (e.g., `my-guard.guard.ts` and `my-guard.guard.spec.ts`).
*/
name: string;
/**
* The path where the guard files should be created, relative to the current workspace. If
* not provided, the guard will be created in the current directory.
*/
path?: string;
/**
* The name of the project where the guard should be created. If not specified, the CLI will
* determine the project from the current directory.
*/
project: string;
/**
* Skip the generation of a unit test file `spec.ts` for the new guard.
*/
skipTests?: boolean;
/**
* The separator character to use before the type within the generated file's name. For
* example, if you set the option to `.`, the file will be named `example.guard.ts`.
*/
typeSeparator?: TypeSeparator;
};
export declare enum Implement {
CanActivate = "CanActivate",
CanActivateChild = "CanActivateChild",
CanDeactivate = "CanDeactivate",
CanMatch = "CanMatch"
}
/**
* The separator character to use before the type within the generated file's name. For
* example, if you set the option to `.`, the file will be named `example.guard.ts`.
*
* The separator character to use before the type within the generated file's name. For
* example, if you set the option to `.`, the file will be named `example.interceptor.ts`.
*
* The separator character to use before the type within the generated file's name. For
* example, if you set the option to `.`, the file will be named `example.pipe.ts`.
*
* The separator character to use before the type within the generated file's name. For
* example, if you set the option to `.`, the file will be named `example.resolver.ts`.
*/
export declare enum TypeSeparator {
Empty = "-",
TypeSeparator = "."
}
/**
* Creates a new interceptor in your project. Interceptors are used to intercept and modify
* HTTP requests and responses before they reach their destination. This allows you to
* perform tasks like adding authentication headers, handling errors, or logging requests.
* This schematic generates the necessary files and boilerplate code for a new interceptor.
*/
export type AngularInterceptorOptionsSchema = {
/**
* Creates the new interceptor files at the top level of the current project. If set to
* false, a new folder with the interceptor's name will be created to contain the files.
*/
flat?: boolean;
/**
* Creates the interceptor as a function `HttpInterceptorFn` instead of a class. Functional
* interceptors can be simpler for basic scenarios.
*/
functional?: boolean;
/**
* The name for the new interceptor. This will be used to create the interceptor's class and
* spec files (e.g., `my-interceptor.interceptor.ts` and
* `my-interceptor.interceptor.spec.ts`).
*/
name: string;
/**
* The path where the interceptor files should be created, relative to the workspace root.
* If not provided, the interceptor will be created in the current directory.
*/
path?: string;
/**
* The name of the project where the interceptor should be created. If not specified, the
* CLI will determine the project from the current directory.
*/
project: string;
/**
* Skip the generation of a unit test file `spec.ts` for the new interceptor.
*/
skipTests?: boolean;
/**
* The separator character to use before the type within the generated file's name. For
* example, if you set the option to `.`, the file will be named `example.interceptor.ts`.
*/
typeSeparator?: TypeSeparator;
};
/**
* Creates a new interface in your project. Interfaces define the structure of objects in
* TypeScript, ensuring type safety and code clarity. This schematic generates a new
* interface with the specified name and type.
*/
export type AngularInterfaceOptionsSchema = {
/**
* The name for the new interface. This will be used to create the interface file (e.g.,
* `my-interface.interface.ts`).
*/
name: string;
/**
* The path where the interface file should be created, relative to the workspace root. If
* not provided, the interface will be created in the current directory.
*/
path?: string;
/**
* A prefix to be added to the interface name. This is typically not used for interfaces, as
* they don't have selectors like components or directives.
*/
prefix?: string;
/**
* The name of the project where the interface should be created. If not specified, the CLI
* will determine the project from the current directory.
*/
project: string;
/**
* Adds a custom type to the filename, allowing you to create more descriptive interface
* names. For example, if you set the type to `data`, the filename will be
* `my-interface.data.ts`.
*/
type?: string;
};
/**
* Creates a new library project in your Angular workspace. Libraries are reusable
* collections of components, services, and other Angular artifacts that can be shared
* across multiple applications. This schematic simplifies the process of generating a new
* library with the necessary files and configurations.
*/
export type LibraryOptionsSchema = {
/**
* The path to the library's public API file, relative to the workspace root. This file
* defines what parts of the library are accessible to applications that import it.
*/
entryFile?: string;
/**
* The name for the new library. This name will be used for the project directory and
* various identifiers within the library's code.
*/
name: string;
/**
* A prefix to be added to the selectors of components generated within this library. For
* example, if the prefix is `my-lib` and you generate a component named `my-component`, the
* selector will be `my-lib-my-component`.
*/
prefix?: string;
/**
* The root directory for the new library, relative to the workspace root. If not specified,
* the library will be created in a subfolder within the `projects` directory, using the
* library's name.
*/
projectRoot?: string;
/**
* Skip the automatic installation of packages. You will need to manually install the
* dependencies later.
*/
skipInstall?: boolean;
/**
* Do not automatically add dependencies to the `package.json` file.
*/
skipPackageJson?: boolean;
/**
* Do not update the workspace `tsconfig.json` file to add a path mapping for the new
* library. The path mapping is needed to use the library in an application, but can be
* disabled here to simplify development.
*/
skipTsConfig?: boolean;
/**
* Create a library that utilizes the standalone API, eliminating the need for NgModules.
* This can simplify the structure of your library and its usage in applications.
*/
standalone?: boolean;
};
/**
* Creates a new Angular workspace and an initial project. This schematic sets up the
* foundation for your Angular development, generating the workspace configuration files and
* an optional starter application. You can customize various aspects of the workspace and
* the initial project, such as routing, styling, and testing.
*/
export type AngularNgNewOptionsSchema = {
/**
* Specifies which AI tools to generate configuration files for. These file are used to
* improve the outputs of AI tools by following the best practices.
*/
aiConfig?: AiConfig[];
/**
* Configure the initial Git commit for the new repository.
*/
commit?: CommitUnion;
/**
* Create a new initial application project in the new workspace. When false, creates an
* empty workspace with no initial application. You can then use the `ng generate
* application` command to create applications in the `projects` directory.
*/
createApplication?: boolean;
/**
* The directory where the new workspace and project should be created. If not specified,
* the workspace will be created in the current directory.
*/
directory?: string;
/**
* Include the styles for the initial application's root component directly within the
* `app.component.ts` file. By default, a separate stylesheet file (e.g.,
* `app.component.css`) is created.
*/
inlineStyle?: boolean;
/**
* Include the HTML template for the initial application's root component directly within
* the `app.component.ts` file. By default, a separate template file (e.g.,
* `app.component.html`) is created.
*/
inlineTemplate?: boolean;
/**
* Generate a minimal Angular workspace without any testing frameworks. This is intended for
* learning purposes and simple experimentation, not for production applications.
*/
minimal?: boolean;
/**
* The name for the new workspace and the initial project. This name will be used for the
* root directory and various identifiers throughout the project.
*/
name: string;
/**
* The path where new projects will be created within the workspace, relative to the
* workspace root. By default, new projects are created in the `projects` directory.
*/
newProjectRoot?: string;
/**
* The package manager used to install dependencies.
*/
packageManager?: PackageManager;
/**
* The prefix to apply to generated selectors for the initial project. For example, if the
* prefix is `my-app` and you generate a component named `my-component`, the selector will
* be `my-app-my-component`.
*/
prefix?: string;
/**
* Enable routing in the initial application project. This sets up the necessary files and
* modules for managing navigation between different views in your application.
*/
routing?: boolean;
/**
* Do not initialize a Git repository in the new workspace. By default, a Git repository is
* initialized to help you track changes to your project.
*/
skipGit?: boolean;
/**
* Skip the automatic installation of packages. You will need to manually install the
* dependencies later.
*/
skipInstall?: boolean;
/**
* Skip the generation of unit test files `spec.ts`.
*/
skipTests?: boolean;
/**
* Configure the initial application for Server-Side Rendering (SSR) and Static Site
* Generation (SSG/Prerendering).
*/
ssr?: boolean;
/**
* Creates an application based upon the standalone API, without NgModules.
*/
standalone?: boolean;
/**
* Enable stricter type checking and stricter bundle budgets settings. This setting helps
* improve maintainability and catch bugs ahead of time. For more information, see
* https://angular.dev/tools/cli/template-typecheck#strict-mode
*/
strict?: boolean;
/**
* The type of stylesheet files to be created for components in the initial project.
*/
style?: SchematicsAngularApplicationStyle;
/**
* The version of the Angular CLI to use.
*/
version: string;
/**
* Sets the view encapsulation mode for components in the initial project. This determines
* how component styles are scoped and applied. Options include: `Emulated` (default, styles
* are scoped to the component), `None` (styles are global), and `ShadowDom` (styles are
* encapsulated using Shadow DOM).
*/
viewEncapsulation?: ViewEncapsulation;
/**
* Create an initial application that does not utilize `zone.js`.
*/
zoneless?: boolean;
};
export declare enum AiConfig {
Claude = "claude",
Copilot = "copilot",
Cursor = "cursor",
Gemini = "gemini",
Jetbrains = "jetbrains",
None = "none",
Windsurf = "windsurf"
}
/**
* Configure the initial Git commit for the new repository.
*/
export type CommitUnion = boolean | CommitObject;
export type CommitObject = {
email: string;
message?: string;
name: string;
[property: string]: any;
};
/**
* Creates a new pipe in your project. Pipes are used to transform data for display in
* templates. They take input values and apply a specific transformation, such as formatting
* dates, currency, or filtering arrays. This schematic generates the necessary files and
* boilerplate code for a new pipe.
*/
export type AngularPipeOptionsSchema = {
/**
* Automatically export the pipe from the specified NgModule, making it accessible to other
* modules in the application.
*/
export?: boolean;
/**
* Creates the new pipe files at the top level of the current project. If set to false, a
* new folder with the pipe's name will be created to contain the files.
*/
flat?: boolean;
/**
* Specify the NgModule where the pipe should be declared. If not provided, the CLI will
* attempt to find the closest NgModule in the pipe's path.
*/
module?: string;
/**
* The name for the new pipe. This will be used to create the pipe's class and spec files
* (e.g., `my-pipe.pipe.ts` and `my-pipe.pipe.spec.ts`).
*/
name: string;
/**
* The path where the pipe files should be created, relative to the workspace root. If not
* provided, the pipe will be created in the current directory.
*/
path?: string;
/**
* The name of the project where the pipe should be created. If not specified, the CLI will
* determine the project from the current directory.
*/
project: string;
/**
* Do not automatically import the new pipe into its closest NgModule.
*/
skipImport?: boolean;
/**
* Prevent the generation of a unit test file `spec.ts` for the new pipe.
*/
skipTests?: boolean;
/**
* Generate a standalone pipe. Standalone pipes are self-contained and don't need to be
* declared in an NgModule. They can be used independently or imported directly into other
* standalone components, directives, or pipes.
*/
standalone?: boolean;
/**
* The separator character to use before the type within the generated file's name. For
* example, if you set the option to `.`, the file will be named `example.pipe.ts`.
*/
typeSeparator?: TypeSeparator;
};
/**
* Creates a new resolver in your project. Resolvers are used to pre-fetch data before a
* route is activated, ensuring that the necessary data is available before the component is
* displayed. This can improve the user experience by preventing delays and loading states.
* This schematic generates a new resolver with the specified name and options.
*/
export type AngularResolverOptionsSchema = {
/**
* Creates the new resolver files at the top level of the current project. If set to false,
* a new folder with the resolver's name will be created to contain the files.
*/
flat?: boolean;
/**
* Creates the resolver as a function `ResolveFn` instead of a class. Functional resolvers
* can be simpler for basic scenarios.
*/
functional?: boolean;
/**
* The name for the new resolver. This will be used to create the resolver's class and spec
* files (e.g., `my-resolver.resolver.ts` and `my-resolver.resolver.spec.ts`).
*/
name: string;
/**
* The path where the resolver files should be created, relative to the current workspace.
* If not provided, the resolver will be created in the current directory.
*/
path?: string;
/**
* The name of the project where the resolver should be created. If not specified, the CLI
* will determine the project from the current directory.
*/
project: string;
/**
* Skip the generation of a unit test file `spec.ts` for the new resolver.
*/
skipTests?: boolean;
/**
* The separator character to use before the type within the generated file's name. For
* example, if you set the option to `.`, the file will be named `example.resolver.ts`.
*/
typeSeparator?: TypeSeparator;
};
/**
* Creates a new service in your project. Services are used to encapsulate reusable logic,
* such as data access, API calls, or utility functions. This schematic simplifies the
* process of generating a new service with the necessary files and boilerplate code.
*/
export type AngularServiceOptionsSchema = {
/**
* Creates files at the top level of the project or the given path. If set to false, a new
* folder with the service's name will be created to contain the files.
*/
flat?: boolean;
/**
* The name for the new service. This will be used to create the service's class and spec
* files (e.g., `my-service.service.ts` and `my-service.service.spec.ts`).
*/
name: string;
/**
* The path where the service files should be created, relative to the workspace root. If
* not provided, the service will be created in the project's `src/app` directory.
*/
path?: string;
/**
* The name of the project where the service should be added. If not specified, the CLI will
* determine the project from the current directory.
*/
project: string;
/**
* Skip the generation of a unit test file `spec.ts` for the service.
*/
skipTests?: boolean;
/**
* Append a custom type to the service's filename. For example, if you set the type to
* `service`, the file will be named `my-service.service.ts`.
*/
type?: string;
};
/**
* Creates a new web worker in your project. Web workers allow you to run JavaScript code in
* the background, improving the performance and responsiveness of your application by
* offloading computationally intensive tasks. This schematic generates the necessary files
* for a new web worker and provides an optional code snippet to demonstrate its usage.
*/
export type AngularWebWorkerOptionsSchema = {
/**
* The name for the new web worker. This will be used to create the worker file (e.g.,
* `my-worker.worker.ts`).
*/
name: string;
/**
* The path where the web worker file should be created, relative to the current workspace.
* If not specified, the worker will be created in the current directory.
*/
path?: string;
/**
* The name of the project where the web worker should be created. If not specified, the CLI
* will determine the project from the current directory.
*/
project: string;
/**
* Generate a code snippet that demonstrates how to create and use the new web worker.
*/
snippet?: boolean;
};

112
node_modules/@angular/cli/lib/config/workspace-schema.js generated vendored Executable file
View File

@@ -0,0 +1,112 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });
exports.AiConfig = exports.TypeSeparator = exports.Implement = exports.SchematicsAngularComponentStyle = exports.ChangeDetection = exports.ViewEncapsulation = exports.SchematicsAngularApplicationStyle = exports.PackageManager = exports.Environment = void 0;
/**
* Configure in which environment disk cache is enabled.
*/
var Environment;
(function (Environment) {
Environment["All"] = "all";
Environment["Ci"] = "ci";
Environment["Local"] = "local";
})(Environment || (exports.Environment = Environment = {}));
/**
* Specify which package manager tool to use.
*
* The package manager used to install dependencies.
*/
var PackageManager;
(function (PackageManager) {
PackageManager["Bun"] = "bun";
PackageManager["Cnpm"] = "cnpm";
PackageManager["Npm"] = "npm";
PackageManager["Pnpm"] = "pnpm";
PackageManager["Yarn"] = "yarn";
})(PackageManager || (exports.PackageManager = PackageManager = {}));
/**
* The type of stylesheet files to be created for components in the application.
*
* The type of stylesheet files to be created for components in the initial project.
*/
var SchematicsAngularApplicationStyle;
(function (SchematicsAngularApplicationStyle) {
SchematicsAngularApplicationStyle["Css"] = "css";
SchematicsAngularApplicationStyle["Less"] = "less";
SchematicsAngularApplicationStyle["Sass"] = "sass";
SchematicsAngularApplicationStyle["Scss"] = "scss";
})(SchematicsAngularApplicationStyle || (exports.SchematicsAngularApplicationStyle = SchematicsAngularApplicationStyle = {}));
/**
* Sets the view encapsulation mode for the application's components. This determines how
* component styles are scoped and applied.
*
* Sets the view encapsulation mode for the component. This determines how the component's
* styles are scoped and applied.
*
* Sets the view encapsulation mode for components in the initial project. This determines
* how component styles are scoped and applied. Options include: `Emulated` (default, styles
* are scoped to the component), `None` (styles are global), and `ShadowDom` (styles are
* encapsulated using Shadow DOM).
*/
var ViewEncapsulation;
(function (ViewEncapsulation) {
ViewEncapsulation["Emulated"] = "Emulated";
ViewEncapsulation["None"] = "None";
ViewEncapsulation["ShadowDom"] = "ShadowDom";
})(ViewEncapsulation || (exports.ViewEncapsulation = ViewEncapsulation = {}));
/**
* Configures the change detection strategy for the component.
*/
var ChangeDetection;
(function (ChangeDetection) {
ChangeDetection["Default"] = "Default";
ChangeDetection["OnPush"] = "OnPush";
})(ChangeDetection || (exports.ChangeDetection = ChangeDetection = {}));
/**
* Specify the type of stylesheet to be created for the component, or `none` to skip
* creating a stylesheet.
*/
var SchematicsAngularComponentStyle;
(function (SchematicsAngularComponentStyle) {
SchematicsAngularComponentStyle["Css"] = "css";
SchematicsAngularComponentStyle["Less"] = "less";
SchematicsAngularComponentStyle["None"] = "none";
SchematicsAngularComponentStyle["Sass"] = "sass";
SchematicsAngularComponentStyle["Scss"] = "scss";
})(SchematicsAngularComponentStyle || (exports.SchematicsAngularComponentStyle = SchematicsAngularComponentStyle = {}));
var Implement;
(function (Implement) {
Implement["CanActivate"] = "CanActivate";
Implement["CanActivateChild"] = "CanActivateChild";
Implement["CanDeactivate"] = "CanDeactivate";
Implement["CanMatch"] = "CanMatch";
})(Implement || (exports.Implement = Implement = {}));
/**
* The separator character to use before the type within the generated file's name. For
* example, if you set the option to `.`, the file will be named `example.guard.ts`.
*
* The separator character to use before the type within the generated file's name. For
* example, if you set the option to `.`, the file will be named `example.interceptor.ts`.
*
* The separator character to use before the type within the generated file's name. For
* example, if you set the option to `.`, the file will be named `example.pipe.ts`.
*
* The separator character to use before the type within the generated file's name. For
* example, if you set the option to `.`, the file will be named `example.resolver.ts`.
*/
var TypeSeparator;
(function (TypeSeparator) {
TypeSeparator["Empty"] = "-";
TypeSeparator["TypeSeparator"] = ".";
})(TypeSeparator || (exports.TypeSeparator = TypeSeparator = {}));
var AiConfig;
(function (AiConfig) {
AiConfig["Claude"] = "claude";
AiConfig["Copilot"] = "copilot";
AiConfig["Cursor"] = "cursor";
AiConfig["Gemini"] = "gemini";
AiConfig["Jetbrains"] = "jetbrains";
AiConfig["None"] = "none";
AiConfig["Windsurf"] = "windsurf";
})(AiConfig || (exports.AiConfig = AiConfig = {}));

8
node_modules/@angular/cli/lib/init.d.ts generated vendored Executable file
View File

@@ -0,0 +1,8 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
export {};

161
node_modules/@angular/cli/lib/init.js generated vendored Executable file
View File

@@ -0,0 +1,161 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
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;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const promises_1 = require("node:fs/promises");
const node_module_1 = require("node:module");
const path = __importStar(require("node:path"));
const semver_1 = require("semver");
const color_1 = require("../src/utilities/color");
const config_1 = require("../src/utilities/config");
const environment_options_1 = require("../src/utilities/environment-options");
const version_1 = require("../src/utilities/version");
/**
* Angular CLI versions prior to v14 may not exit correctly if not forcibly exited
* via `process.exit()`. When bootstrapping, `forceExit` will be set to `true`
* if the local CLI version is less than v14 to prevent the CLI from hanging on
* exit in those cases.
*/
let forceExit = false;
(async () => {
/**
* Disable Browserslist old data warning as otherwise with every release we'd need to update this dependency
* which is cumbersome considering we pin versions and the warning is not user actionable.
* `Browserslist: caniuse-lite is outdated. Please run next command `npm update`
* See: https://github.com/browserslist/browserslist/blob/819c4337456996d19db6ba953014579329e9c6e1/node.js#L324
*/
process.env.BROWSERSLIST_IGNORE_OLD_DATA = '1';
const rawCommandName = process.argv[2];
/**
* Disable CLI version mismatch checks and forces usage of the invoked CLI
* instead of invoking the local installed version.
*
* When running `ng new` always favor the global version. As in some
* cases orphan `node_modules` would cause the non global CLI to be used.
* @see: https://github.com/angular/angular-cli/issues/14603
*/
if (environment_options_1.disableVersionCheck || rawCommandName === 'new') {
return (await Promise.resolve().then(() => __importStar(require('./cli')))).default;
}
let cli;
try {
// No error implies a projectLocalCli, which will load whatever
// version of ng-cli you have installed in a local package.json
const cwdRequire = (0, node_module_1.createRequire)(process.cwd() + '/');
const projectLocalCli = cwdRequire.resolve('@angular/cli');
cli = await Promise.resolve(`${projectLocalCli}`).then(s => __importStar(require(s)));
const globalVersion = new semver_1.SemVer(version_1.VERSION.full);
// Older versions might not have the VERSION export
let localVersion = cli.VERSION?.full;
if (!localVersion) {
try {
const localPackageJson = await (0, promises_1.readFile)(path.join(path.dirname(projectLocalCli), '../../package.json'), 'utf-8');
localVersion = JSON.parse(localPackageJson).version;
}
catch (error) {
// eslint-disable-next-line no-console
console.error('Version mismatch check skipped. Unable to retrieve local version: ' + error);
}
}
// Ensure older versions of the CLI fully exit
const localMajorVersion = (0, semver_1.major)(localVersion);
if (localMajorVersion > 0 && localMajorVersion < 14) {
forceExit = true;
// Versions prior to 14 didn't implement completion command.
if (rawCommandName === 'completion') {
return null;
}
}
let isGlobalGreater = false;
try {
isGlobalGreater = localVersion > 0 && globalVersion.compare(localVersion) > 0;
}
catch (error) {
// eslint-disable-next-line no-console
console.error('Version mismatch check skipped. Unable to compare local version: ' + error);
}
// When using the completion command, don't show the warning as otherwise this will break completion.
if (isGlobalGreater &&
rawCommandName !== '--get-yargs-completions' &&
rawCommandName !== 'completion') {
// If using the update command and the global version is greater, use the newer update command
// This allows improvements in update to be used in older versions that do not have bootstrapping
if (rawCommandName === 'update' &&
cli.VERSION &&
cli.VERSION.major - globalVersion.major <= 1) {
cli = await Promise.resolve().then(() => __importStar(require('./cli')));
}
else if (await (0, config_1.isWarningEnabled)('versionMismatch')) {
// Otherwise, use local version and warn if global is newer than local
const warning = `Your global Angular CLI version (${globalVersion}) is greater than your local ` +
`version (${localVersion}). The local Angular CLI version is used.\n\n` +
'To disable this warning use "ng config -g cli.warnings.versionMismatch false".';
// eslint-disable-next-line no-console
console.error(color_1.colors.yellow(warning));
}
}
}
catch {
// If there is an error, resolve could not find the ng-cli
// library from a package.json. Instead, include it from a relative
// path to this script file (which is likely a globally installed
// npm package). Most common cause for hitting this is `ng new`
cli = await Promise.resolve().then(() => __importStar(require('./cli')));
}
if ('default' in cli) {
cli = cli['default'];
}
return cli;
})()
.then((cli) => cli?.({
cliArgs: process.argv.slice(2),
}))
.then((exitCode = 0) => {
if (forceExit) {
process.exit(exitCode);
}
process.exitCode = exitCode;
})
.catch((err) => {
// eslint-disable-next-line no-console
console.error('Unknown error: ' + err.toString());
process.exit(127);
});

View File

@@ -0,0 +1,22 @@
Copyright (c) 2025 Simon Boudrias
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,188 @@
# `@inquirer/checkbox`
Simple interactive command line prompt to display a list of checkboxes (multi select).
![Checkbox prompt](https://cdn.rawgit.com/SBoudrias/Inquirer.js/28ae8337ba51d93e359ef4f7ee24e79b69898962/assets/screenshots/checkbox.svg)
# Installation
<table>
<tr>
<th>npm</th>
<th>yarn</th>
</tr>
<tr>
<td>
```sh
npm install @inquirer/prompts
```
</td>
<td>
```sh
yarn add @inquirer/prompts
```
</td>
</tr>
<tr>
<td colSpan="2" align="center">Or</td>
</tr>
<tr>
<td>
```sh
npm install @inquirer/checkbox
```
</td>
<td>
```sh
yarn add @inquirer/checkbox
```
</td>
</tr>
</table>
# Usage
```js
import { checkbox, Separator } from '@inquirer/prompts';
// Or
// import checkbox, { Separator } from '@inquirer/checkbox';
const answer = await checkbox({
message: 'Select a package manager',
choices: [
{ name: 'npm', value: 'npm' },
{ name: 'yarn', value: 'yarn' },
new Separator(),
{ name: 'pnpm', value: 'pnpm', disabled: true },
{
name: 'pnpm',
value: 'pnpm',
disabled: '(pnpm is not available)',
},
],
});
```
## Options
| Property | Type | Required | Description |
| --------- | --------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| message | `string` | yes | The question to ask |
| choices | `Choice[]` | yes | List of the available choices. |
| pageSize | `number` | no | By default, lists of choice longer than 7 will be paginated. Use this option to control how many choices will appear on the screen at once. |
| loop | `boolean` | no | Defaults to `true`. When set to `false`, the cursor will be constrained to the top and bottom of the choice list without looping. |
| required | `boolean` | no | When set to `true`, ensures at least one choice must be selected. |
| validate | `async (Choice[]) => boolean \| string` | no | On submit, validate the choices. When returning a string, it'll be used as the error message displayed to the user. Note: returning a rejected promise, we'll assume a code error happened and crash. |
| shortcuts | [See Shortcuts](#Shortcuts) | no | Customize shortcut keys for `all` and `invert`. |
| theme | [See Theming](#Theming) | no | Customize look of the prompt. |
`Separator` objects can be used in the `choices` array to render non-selectable lines in the choice list. By default it'll render a line, but you can provide the text as argument (`new Separator('-- Dependencies --')`). This option is often used to add labels to groups within long list of options.
### `Choice` object
The `Choice` object is typed as
```ts
type Choice<Value> = {
value: Value;
name?: string;
checkedName?: string;
description?: string;
short?: string;
checked?: boolean;
disabled?: boolean | string;
};
```
Here's each property:
- `value`: The value is what will be returned by `await checkbox()`.
- `name`: This is the string displayed in the choice list.
- `checkedName`: Alternative `name` (or format) displayed when the choice is checked.
- `description`: Option for a longer description string that'll appear under the list when the cursor highlight a given choice.
- `short`: Once the prompt is done (press enter), we'll use `short` if defined to render next to the question. By default we'll use `name`.
- `checked`: If `true`, the option will be checked by default.
- `disabled`: Disallow the option from being selected. If `disabled` is a string, it'll be used as a help tip explaining why the choice isn't available.
Also note the `choices` array can contain `Separator`s to help organize long lists.
`choices` can also be an array of string, in which case the string will be used both as the `value` and the `name`.
## Shortcuts
You can customize the shortcut keys for `all` and `invert` or disable them by setting them to `null`.
```ts
type Shortcuts = {
all?: string | null; // default: 'a'
invert?: string | null; // default: 'i'
};
```
## Theming
You can theme a prompt by passing a `theme` object option. The theme object only need to includes the keys you wish to modify, we'll fallback on the defaults for the rest.
```ts
type Theme = {
prefix: string | { idle: string; done: string };
spinner: {
interval: number;
frames: string[];
};
style: {
answer: (text: string) => string;
message: (text: string, status: 'idle' | 'done' | 'loading') => string;
error: (text: string) => string;
defaultAnswer: (text: string) => string;
help: (text: string) => string;
highlight: (text: string) => string;
key: (text: string) => string;
disabledChoice: (text: string) => string;
description: (text: string) => string;
renderSelectedChoices: <T>(
selectedChoices: ReadonlyArray<Choice<T>>,
allChoices: ReadonlyArray<Choice<T> | Separator>,
) => string;
keysHelpTip: (keys: [key: string, action: string][]) => string | undefined;
};
icon: {
checked: string;
unchecked: string;
cursor: string;
};
};
```
### `theme.style.keysHelpTip`
This function allows you to customize the keyboard shortcuts help tip displayed below the prompt. It receives an array of key-action pairs and should return a formatted string. You can also hook here to localize the labels to different languages.
It can also returns `undefined` to hide the help tip entirely. This is the replacement for the deprecated theme option `helpMode: 'never'`.
```js
theme: {
style: {
keysHelpTip: (keys) => {
// Return undefined to hide the help tip completely
return undefined;
// Or customize the formatting. Or localize the labels.
return keys.map(([key, action]) => `${key}: ${action}`).join(' | ');
};
}
}
```
# License
Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
Licensed under the MIT license.

View File

@@ -0,0 +1,57 @@
import { Separator, type Theme, type Keybinding } from '@inquirer/core';
import type { PartialDeep } from '@inquirer/type';
type CheckboxTheme = {
icon: {
checked: string;
unchecked: string;
cursor: string;
};
style: {
disabledChoice: (text: string) => string;
renderSelectedChoices: <T>(selectedChoices: ReadonlyArray<NormalizedChoice<T>>, allChoices: ReadonlyArray<NormalizedChoice<T> | Separator>) => string;
description: (text: string) => string;
keysHelpTip: (keys: [key: string, action: string][]) => string | undefined;
};
/** @deprecated Use theme.style.keysHelpTip instead */
helpMode: 'always' | 'never' | 'auto';
keybindings: ReadonlyArray<Keybinding>;
};
type CheckboxShortcuts = {
all?: string | null;
invert?: string | null;
};
type Choice<Value> = {
value: Value;
name?: string;
checkedName?: string;
description?: string;
short?: string;
disabled?: boolean | string;
checked?: boolean;
type?: never;
};
type NormalizedChoice<Value> = {
value: Value;
name: string;
checkedName: string;
description?: string;
short: string;
disabled: boolean | string;
checked: boolean;
};
declare const _default: <Value>(config: {
message: string;
prefix?: string | undefined;
pageSize?: number | undefined;
instructions?: string | boolean | undefined;
choices: readonly (string | Separator)[] | readonly (Separator | Choice<Value>)[];
loop?: boolean | undefined;
required?: boolean | undefined;
validate?: ((choices: readonly NormalizedChoice<Value>[]) => boolean | string | Promise<string | boolean>) | undefined;
theme?: PartialDeep<Theme<CheckboxTheme>> | undefined;
shortcuts?: CheckboxShortcuts | undefined;
}, context?: import("@inquirer/type").Context) => Promise<Value[]> & {
cancel: () => void;
};
export default _default;
export { Separator } from '@inquirer/core';

View File

@@ -0,0 +1,209 @@
"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 core_1 = require("@inquirer/core");
const ansi_1 = require("@inquirer/ansi");
const yoctocolors_cjs_1 = __importDefault(require("yoctocolors-cjs"));
const figures_1 = __importDefault(require("@inquirer/figures"));
const checkboxTheme = {
icon: {
checked: yoctocolors_cjs_1.default.green(figures_1.default.circleFilled),
unchecked: figures_1.default.circle,
cursor: figures_1.default.pointer,
},
style: {
disabledChoice: (text) => yoctocolors_cjs_1.default.dim(`- ${text}`),
renderSelectedChoices: (selectedChoices) => selectedChoices.map((choice) => choice.short).join(', '),
description: (text) => yoctocolors_cjs_1.default.cyan(text),
keysHelpTip: (keys) => keys
.map(([key, action]) => `${yoctocolors_cjs_1.default.bold(key)} ${yoctocolors_cjs_1.default.dim(action)}`)
.join(yoctocolors_cjs_1.default.dim(' • ')),
},
helpMode: 'always',
keybindings: [],
};
function isSelectable(item) {
return !core_1.Separator.isSeparator(item) && !item.disabled;
}
function isChecked(item) {
return isSelectable(item) && item.checked;
}
function toggle(item) {
return isSelectable(item) ? { ...item, checked: !item.checked } : item;
}
function check(checked) {
return function (item) {
return isSelectable(item) ? { ...item, checked } : item;
};
}
function normalizeChoices(choices) {
return choices.map((choice) => {
if (core_1.Separator.isSeparator(choice))
return choice;
if (typeof choice === 'string') {
return {
value: choice,
name: choice,
short: choice,
checkedName: choice,
disabled: false,
checked: false,
};
}
const name = choice.name ?? String(choice.value);
const normalizedChoice = {
value: choice.value,
name,
short: choice.short ?? name,
checkedName: choice.checkedName ?? name,
disabled: choice.disabled ?? false,
checked: choice.checked ?? false,
};
if (choice.description) {
normalizedChoice.description = choice.description;
}
return normalizedChoice;
});
}
exports.default = (0, core_1.createPrompt)((config, done) => {
const {
// eslint-disable-next-line @typescript-eslint/no-deprecated
instructions, pageSize = 7, loop = true, required, validate = () => true, } = config;
const shortcuts = { all: 'a', invert: 'i', ...config.shortcuts };
const theme = (0, core_1.makeTheme)(checkboxTheme, config.theme);
const { keybindings } = theme;
const [status, setStatus] = (0, core_1.useState)('idle');
const prefix = (0, core_1.usePrefix)({ status, theme });
const [items, setItems] = (0, core_1.useState)(normalizeChoices(config.choices));
const bounds = (0, core_1.useMemo)(() => {
const first = items.findIndex(isSelectable);
const last = items.findLastIndex(isSelectable);
if (first === -1) {
throw new core_1.ValidationError('[checkbox prompt] No selectable choices. All choices are disabled.');
}
return { first, last };
}, [items]);
const [active, setActive] = (0, core_1.useState)(bounds.first);
const [errorMsg, setError] = (0, core_1.useState)();
(0, core_1.useKeypress)(async (key) => {
if ((0, core_1.isEnterKey)(key)) {
const selection = items.filter(isChecked);
const isValid = await validate([...selection]);
if (required && !items.some(isChecked)) {
setError('At least one choice must be selected');
}
else if (isValid === true) {
setStatus('done');
done(selection.map((choice) => choice.value));
}
else {
setError(isValid || 'You must select a valid value');
}
}
else if ((0, core_1.isUpKey)(key, keybindings) || (0, core_1.isDownKey)(key, keybindings)) {
if (loop ||
((0, core_1.isUpKey)(key, keybindings) && active !== bounds.first) ||
((0, core_1.isDownKey)(key, keybindings) && active !== bounds.last)) {
const offset = (0, core_1.isUpKey)(key, keybindings) ? -1 : 1;
let next = active;
do {
next = (next + offset + items.length) % items.length;
} while (!isSelectable(items[next]));
setActive(next);
}
}
else if ((0, core_1.isSpaceKey)(key)) {
setError(undefined);
setItems(items.map((choice, i) => (i === active ? toggle(choice) : choice)));
}
else if (key.name === shortcuts.all) {
const selectAll = items.some((choice) => isSelectable(choice) && !choice.checked);
setItems(items.map(check(selectAll)));
}
else if (key.name === shortcuts.invert) {
setItems(items.map(toggle));
}
else if ((0, core_1.isNumberKey)(key)) {
const selectedIndex = Number(key.name) - 1;
// Find the nth item (ignoring separators)
let selectableIndex = -1;
const position = items.findIndex((item) => {
if (core_1.Separator.isSeparator(item))
return false;
selectableIndex++;
return selectableIndex === selectedIndex;
});
const selectedItem = items[position];
if (selectedItem && isSelectable(selectedItem)) {
setActive(position);
setItems(items.map((choice, i) => (i === position ? toggle(choice) : choice)));
}
}
});
const message = theme.style.message(config.message, status);
let description;
const page = (0, core_1.usePagination)({
items,
active,
renderItem({ item, isActive }) {
if (core_1.Separator.isSeparator(item)) {
return ` ${item.separator}`;
}
if (item.disabled) {
const disabledLabel = typeof item.disabled === 'string' ? item.disabled : '(disabled)';
return theme.style.disabledChoice(`${item.name} ${disabledLabel}`);
}
if (isActive) {
description = item.description;
}
const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
const name = item.checked ? item.checkedName : item.name;
const color = isActive ? theme.style.highlight : (x) => x;
const cursor = isActive ? theme.icon.cursor : ' ';
return color(`${cursor}${checkbox} ${name}`);
},
pageSize,
loop,
});
if (status === 'done') {
const selection = items.filter(isChecked);
const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items));
return [prefix, message, answer].filter(Boolean).join(' ');
}
let helpLine;
// eslint-disable-next-line @typescript-eslint/no-deprecated
if (theme.helpMode !== 'never' && instructions !== false) {
if (typeof instructions === 'string') {
helpLine = instructions;
}
else {
const keys = [
['↑↓', 'navigate'],
['space', 'select'],
];
if (shortcuts.all)
keys.push([shortcuts.all, 'all']);
if (shortcuts.invert)
keys.push([shortcuts.invert, 'invert']);
keys.push(['⏎', 'submit']);
helpLine = theme.style.keysHelpTip(keys);
}
}
const lines = [
[prefix, message].filter(Boolean).join(' '),
page,
' ',
description ? theme.style.description(description) : '',
errorMsg ? theme.style.error(errorMsg) : '',
helpLine,
]
.filter(Boolean)
.join('\n')
.trimEnd();
return `${lines}${ansi_1.cursorHide}`;
});
var core_2 = require("@inquirer/core");
Object.defineProperty(exports, "Separator", { enumerable: true, get: function () { return core_2.Separator; } });

View File

@@ -0,0 +1,3 @@
{
"type": "commonjs"
}

View File

@@ -0,0 +1,57 @@
import { Separator, type Theme, type Keybinding } from '@inquirer/core';
import type { PartialDeep } from '@inquirer/type';
type CheckboxTheme = {
icon: {
checked: string;
unchecked: string;
cursor: string;
};
style: {
disabledChoice: (text: string) => string;
renderSelectedChoices: <T>(selectedChoices: ReadonlyArray<NormalizedChoice<T>>, allChoices: ReadonlyArray<NormalizedChoice<T> | Separator>) => string;
description: (text: string) => string;
keysHelpTip: (keys: [key: string, action: string][]) => string | undefined;
};
/** @deprecated Use theme.style.keysHelpTip instead */
helpMode: 'always' | 'never' | 'auto';
keybindings: ReadonlyArray<Keybinding>;
};
type CheckboxShortcuts = {
all?: string | null;
invert?: string | null;
};
type Choice<Value> = {
value: Value;
name?: string;
checkedName?: string;
description?: string;
short?: string;
disabled?: boolean | string;
checked?: boolean;
type?: never;
};
type NormalizedChoice<Value> = {
value: Value;
name: string;
checkedName: string;
description?: string;
short: string;
disabled: boolean | string;
checked: boolean;
};
declare const _default: <Value>(config: {
message: string;
prefix?: string | undefined;
pageSize?: number | undefined;
instructions?: string | boolean | undefined;
choices: readonly (string | Separator)[] | readonly (Separator | Choice<Value>)[];
loop?: boolean | undefined;
required?: boolean | undefined;
validate?: ((choices: readonly NormalizedChoice<Value>[]) => boolean | string | Promise<string | boolean>) | undefined;
theme?: PartialDeep<Theme<CheckboxTheme>> | undefined;
shortcuts?: CheckboxShortcuts | undefined;
}, context?: import("@inquirer/type").Context) => Promise<Value[]> & {
cancel: () => void;
};
export default _default;
export { Separator } from '@inquirer/core';

View File

@@ -0,0 +1,202 @@
import { createPrompt, useState, useKeypress, usePrefix, usePagination, useMemo, makeTheme, isUpKey, isDownKey, isSpaceKey, isNumberKey, isEnterKey, ValidationError, Separator, } from '@inquirer/core';
import { cursorHide } from '@inquirer/ansi';
import colors from 'yoctocolors-cjs';
import figures from '@inquirer/figures';
const checkboxTheme = {
icon: {
checked: colors.green(figures.circleFilled),
unchecked: figures.circle,
cursor: figures.pointer,
},
style: {
disabledChoice: (text) => colors.dim(`- ${text}`),
renderSelectedChoices: (selectedChoices) => selectedChoices.map((choice) => choice.short).join(', '),
description: (text) => colors.cyan(text),
keysHelpTip: (keys) => keys
.map(([key, action]) => `${colors.bold(key)} ${colors.dim(action)}`)
.join(colors.dim(' • ')),
},
helpMode: 'always',
keybindings: [],
};
function isSelectable(item) {
return !Separator.isSeparator(item) && !item.disabled;
}
function isChecked(item) {
return isSelectable(item) && item.checked;
}
function toggle(item) {
return isSelectable(item) ? { ...item, checked: !item.checked } : item;
}
function check(checked) {
return function (item) {
return isSelectable(item) ? { ...item, checked } : item;
};
}
function normalizeChoices(choices) {
return choices.map((choice) => {
if (Separator.isSeparator(choice))
return choice;
if (typeof choice === 'string') {
return {
value: choice,
name: choice,
short: choice,
checkedName: choice,
disabled: false,
checked: false,
};
}
const name = choice.name ?? String(choice.value);
const normalizedChoice = {
value: choice.value,
name,
short: choice.short ?? name,
checkedName: choice.checkedName ?? name,
disabled: choice.disabled ?? false,
checked: choice.checked ?? false,
};
if (choice.description) {
normalizedChoice.description = choice.description;
}
return normalizedChoice;
});
}
export default createPrompt((config, done) => {
const {
// eslint-disable-next-line @typescript-eslint/no-deprecated
instructions, pageSize = 7, loop = true, required, validate = () => true, } = config;
const shortcuts = { all: 'a', invert: 'i', ...config.shortcuts };
const theme = makeTheme(checkboxTheme, config.theme);
const { keybindings } = theme;
const [status, setStatus] = useState('idle');
const prefix = usePrefix({ status, theme });
const [items, setItems] = useState(normalizeChoices(config.choices));
const bounds = useMemo(() => {
const first = items.findIndex(isSelectable);
const last = items.findLastIndex(isSelectable);
if (first === -1) {
throw new ValidationError('[checkbox prompt] No selectable choices. All choices are disabled.');
}
return { first, last };
}, [items]);
const [active, setActive] = useState(bounds.first);
const [errorMsg, setError] = useState();
useKeypress(async (key) => {
if (isEnterKey(key)) {
const selection = items.filter(isChecked);
const isValid = await validate([...selection]);
if (required && !items.some(isChecked)) {
setError('At least one choice must be selected');
}
else if (isValid === true) {
setStatus('done');
done(selection.map((choice) => choice.value));
}
else {
setError(isValid || 'You must select a valid value');
}
}
else if (isUpKey(key, keybindings) || isDownKey(key, keybindings)) {
if (loop ||
(isUpKey(key, keybindings) && active !== bounds.first) ||
(isDownKey(key, keybindings) && active !== bounds.last)) {
const offset = isUpKey(key, keybindings) ? -1 : 1;
let next = active;
do {
next = (next + offset + items.length) % items.length;
} while (!isSelectable(items[next]));
setActive(next);
}
}
else if (isSpaceKey(key)) {
setError(undefined);
setItems(items.map((choice, i) => (i === active ? toggle(choice) : choice)));
}
else if (key.name === shortcuts.all) {
const selectAll = items.some((choice) => isSelectable(choice) && !choice.checked);
setItems(items.map(check(selectAll)));
}
else if (key.name === shortcuts.invert) {
setItems(items.map(toggle));
}
else if (isNumberKey(key)) {
const selectedIndex = Number(key.name) - 1;
// Find the nth item (ignoring separators)
let selectableIndex = -1;
const position = items.findIndex((item) => {
if (Separator.isSeparator(item))
return false;
selectableIndex++;
return selectableIndex === selectedIndex;
});
const selectedItem = items[position];
if (selectedItem && isSelectable(selectedItem)) {
setActive(position);
setItems(items.map((choice, i) => (i === position ? toggle(choice) : choice)));
}
}
});
const message = theme.style.message(config.message, status);
let description;
const page = usePagination({
items,
active,
renderItem({ item, isActive }) {
if (Separator.isSeparator(item)) {
return ` ${item.separator}`;
}
if (item.disabled) {
const disabledLabel = typeof item.disabled === 'string' ? item.disabled : '(disabled)';
return theme.style.disabledChoice(`${item.name} ${disabledLabel}`);
}
if (isActive) {
description = item.description;
}
const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
const name = item.checked ? item.checkedName : item.name;
const color = isActive ? theme.style.highlight : (x) => x;
const cursor = isActive ? theme.icon.cursor : ' ';
return color(`${cursor}${checkbox} ${name}`);
},
pageSize,
loop,
});
if (status === 'done') {
const selection = items.filter(isChecked);
const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items));
return [prefix, message, answer].filter(Boolean).join(' ');
}
let helpLine;
// eslint-disable-next-line @typescript-eslint/no-deprecated
if (theme.helpMode !== 'never' && instructions !== false) {
if (typeof instructions === 'string') {
helpLine = instructions;
}
else {
const keys = [
['↑↓', 'navigate'],
['space', 'select'],
];
if (shortcuts.all)
keys.push([shortcuts.all, 'all']);
if (shortcuts.invert)
keys.push([shortcuts.invert, 'invert']);
keys.push(['⏎', 'submit']);
helpLine = theme.style.keysHelpTip(keys);
}
}
const lines = [
[prefix, message].filter(Boolean).join(' '),
page,
' ',
description ? theme.style.description(description) : '',
errorMsg ? theme.style.error(errorMsg) : '',
helpLine,
]
.filter(Boolean)
.join('\n')
.trimEnd();
return `${lines}${cursorHide}`;
});
export { Separator } from '@inquirer/core';

View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@@ -0,0 +1,113 @@
{
"name": "@inquirer/checkbox",
"version": "4.3.2",
"description": "Inquirer checkbox prompt",
"keywords": [
"answer",
"answers",
"ask",
"base",
"cli",
"command",
"command-line",
"confirm",
"enquirer",
"generate",
"generator",
"hyper",
"input",
"inquire",
"inquirer",
"interface",
"iterm",
"javascript",
"menu",
"node",
"nodejs",
"prompt",
"promptly",
"prompts",
"question",
"readline",
"scaffold",
"scaffolder",
"scaffolding",
"stdin",
"stdout",
"terminal",
"tty",
"ui",
"yeoman",
"yo",
"zsh"
],
"homepage": "https://github.com/SBoudrias/Inquirer.js/blob/main/packages/checkbox/README.md",
"repository": {
"type": "git",
"url": "https://github.com/SBoudrias/Inquirer.js.git"
},
"license": "MIT",
"author": "Simon Boudrias <admin@simonboudrias.com>",
"sideEffects": false,
"type": "module",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.js"
}
}
},
"main": "./dist/commonjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/commonjs/index.d.ts",
"files": [
"dist"
],
"scripts": {
"attw": "attw --pack",
"tsc": "tshy"
},
"dependencies": {
"@inquirer/ansi": "^1.0.2",
"@inquirer/core": "^10.3.2",
"@inquirer/figures": "^1.0.15",
"@inquirer/type": "^3.0.10",
"yoctocolors-cjs": "^2.1.3"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.2",
"@inquirer/testing": "^2.1.53",
"@repo/tsconfig": "0.0.0",
"tshy": "^3.0.3"
},
"engines": {
"node": ">=18"
},
"publishConfig": {
"access": "public"
},
"tshy": {
"exclude": [
"src/**/*.test.ts"
],
"exports": {
"./package.json": "./package.json",
".": "./src/index.ts"
}
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
},
"gitHead": "4731a373881368e2f701c41adc67bc83244bf89f"
}

View File

@@ -0,0 +1,22 @@
Copyright (c) 2025 Simon Boudrias
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,92 @@
# `@inquirer/confirm`
Simple interactive command line prompt to gather boolean input from users.
![Confirm prompt](https://cdn.rawgit.com/SBoudrias/Inquirer.js/28ae8337ba51d93e359ef4f7ee24e79b69898962/assets/screenshots/confirm.svg)
# Installation
<table>
<tr>
<th>npm</th>
<th>yarn</th>
</tr>
<tr>
<td>
```sh
npm install @inquirer/prompts
```
</td>
<td>
```sh
yarn add @inquirer/prompts
```
</td>
</tr>
<tr>
<td colSpan="2" align="center">Or</td>
</tr>
<tr>
<td>
```sh
npm install @inquirer/confirm
```
</td>
<td>
```sh
yarn add @inquirer/confirm
```
</td>
</tr>
</table>
# Usage
```js
import { confirm } from '@inquirer/prompts';
// Or
// import confirm from '@inquirer/confirm';
const answer = await confirm({ message: 'Continue?' });
```
## Options
| Property | Type | Required | Description |
| ----------- | ----------------------- | -------- | ------------------------------------------------------- |
| message | `string` | yes | The question to ask |
| default | `boolean` | no | Default answer (true or false) |
| transformer | `(boolean) => string` | no | Transform the prompt printed message to a custom string |
| theme | [See Theming](#Theming) | no | Customize look of the prompt. |
## Theming
You can theme a prompt by passing a `theme` object option. The theme object only need to includes the keys you wish to modify, we'll fallback on the defaults for the rest.
```ts
type Theme = {
prefix: string | { idle: string; done: string };
spinner: {
interval: number;
frames: string[];
};
style: {
answer: (text: string) => string;
message: (text: string, status: 'idle' | 'done' | 'loading') => string;
defaultAnswer: (text: string) => string;
};
};
```
# License
Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
Licensed under the MIT license.

View File

@@ -0,0 +1,10 @@
import { type Theme } from '@inquirer/core';
import type { PartialDeep } from '@inquirer/type';
type ConfirmConfig = {
message: string;
default?: boolean;
transformer?: (value: boolean) => string;
theme?: PartialDeep<Theme>;
};
declare const _default: import("@inquirer/type").Prompt<boolean, ConfirmConfig>;
export default _default;

View File

@@ -0,0 +1,50 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@inquirer/core");
function getBooleanValue(value, defaultValue) {
let answer = defaultValue !== false;
if (/^(y|yes)/i.test(value))
answer = true;
else if (/^(n|no)/i.test(value))
answer = false;
return answer;
}
function boolToString(value) {
return value ? 'Yes' : 'No';
}
exports.default = (0, core_1.createPrompt)((config, done) => {
const { transformer = boolToString } = config;
const [status, setStatus] = (0, core_1.useState)('idle');
const [value, setValue] = (0, core_1.useState)('');
const theme = (0, core_1.makeTheme)(config.theme);
const prefix = (0, core_1.usePrefix)({ status, theme });
(0, core_1.useKeypress)((key, rl) => {
if (status !== 'idle')
return;
if ((0, core_1.isEnterKey)(key)) {
const answer = getBooleanValue(value, config.default);
setValue(transformer(answer));
setStatus('done');
done(answer);
}
else if ((0, core_1.isTabKey)(key)) {
const answer = boolToString(!getBooleanValue(value, config.default));
rl.clearLine(0); // Remove the tab character.
rl.write(answer);
setValue(answer);
}
else {
setValue(rl.line);
}
});
let formattedValue = value;
let defaultValue = '';
if (status === 'done') {
formattedValue = theme.style.answer(value);
}
else {
defaultValue = ` ${theme.style.defaultAnswer(config.default === false ? 'y/N' : 'Y/n')}`;
}
const message = theme.style.message(config.message, status);
return `${prefix} ${message}${defaultValue} ${formattedValue}`;
});

View File

@@ -0,0 +1,3 @@
{
"type": "commonjs"
}

View File

@@ -0,0 +1,10 @@
import { type Theme } from '@inquirer/core';
import type { PartialDeep } from '@inquirer/type';
type ConfirmConfig = {
message: string;
default?: boolean;
transformer?: (value: boolean) => string;
theme?: PartialDeep<Theme>;
};
declare const _default: import("@inquirer/type").Prompt<boolean, ConfirmConfig>;
export default _default;

View File

@@ -0,0 +1,48 @@
import { createPrompt, useState, useKeypress, isEnterKey, isTabKey, usePrefix, makeTheme, } from '@inquirer/core';
function getBooleanValue(value, defaultValue) {
let answer = defaultValue !== false;
if (/^(y|yes)/i.test(value))
answer = true;
else if (/^(n|no)/i.test(value))
answer = false;
return answer;
}
function boolToString(value) {
return value ? 'Yes' : 'No';
}
export default createPrompt((config, done) => {
const { transformer = boolToString } = config;
const [status, setStatus] = useState('idle');
const [value, setValue] = useState('');
const theme = makeTheme(config.theme);
const prefix = usePrefix({ status, theme });
useKeypress((key, rl) => {
if (status !== 'idle')
return;
if (isEnterKey(key)) {
const answer = getBooleanValue(value, config.default);
setValue(transformer(answer));
setStatus('done');
done(answer);
}
else if (isTabKey(key)) {
const answer = boolToString(!getBooleanValue(value, config.default));
rl.clearLine(0); // Remove the tab character.
rl.write(answer);
setValue(answer);
}
else {
setValue(rl.line);
}
});
let formattedValue = value;
let defaultValue = '';
if (status === 'done') {
formattedValue = theme.style.answer(value);
}
else {
defaultValue = ` ${theme.style.defaultAnswer(config.default === false ? 'y/N' : 'Y/n')}`;
}
const message = theme.style.message(config.message, status);
return `${prefix} ${message}${defaultValue} ${formattedValue}`;
});

View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@@ -0,0 +1,110 @@
{
"name": "@inquirer/confirm",
"version": "5.1.21",
"description": "Inquirer confirm prompt",
"keywords": [
"answer",
"answers",
"ask",
"base",
"cli",
"command",
"command-line",
"confirm",
"enquirer",
"generate",
"generator",
"hyper",
"input",
"inquire",
"inquirer",
"interface",
"iterm",
"javascript",
"menu",
"node",
"nodejs",
"prompt",
"promptly",
"prompts",
"question",
"readline",
"scaffold",
"scaffolder",
"scaffolding",
"stdin",
"stdout",
"terminal",
"tty",
"ui",
"yeoman",
"yo",
"zsh"
],
"homepage": "https://github.com/SBoudrias/Inquirer.js/blob/main/packages/confirm/README.md",
"repository": {
"type": "git",
"url": "https://github.com/SBoudrias/Inquirer.js.git"
},
"license": "MIT",
"author": "Simon Boudrias <admin@simonboudrias.com>",
"sideEffects": false,
"type": "module",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.js"
}
}
},
"main": "./dist/commonjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/commonjs/index.d.ts",
"files": [
"dist"
],
"scripts": {
"attw": "attw --pack",
"tsc": "tshy"
},
"dependencies": {
"@inquirer/core": "^10.3.2",
"@inquirer/type": "^3.0.10"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.2",
"@inquirer/testing": "^2.1.53",
"@repo/tsconfig": "0.0.0",
"tshy": "^3.0.3"
},
"engines": {
"node": ">=18"
},
"publishConfig": {
"access": "public"
},
"tshy": {
"exclude": [
"src/**/*.test.ts"
],
"exports": {
"./package.json": "./package.json",
".": "./src/index.ts"
}
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
},
"gitHead": "4731a373881368e2f701c41adc67bc83244bf89f"
}

View File

@@ -0,0 +1,22 @@
Copyright (c) 2025 Simon Boudrias
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,383 @@
# `@inquirer/core`
The `@inquirer/core` package is the library enabling the creation of Inquirer prompts.
It aims to implements a lightweight API similar to React hooks - but without JSX.
# Installation
<table>
<tr>
<th>npm</th>
<th>yarn</th>
</tr>
<tr>
<td>
```sh
npm install @inquirer/core
```
</td>
<td>
```sh
yarn add @inquirer/core
```
</td>
</tr>
</table>
# Usage
## 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 { createPrompt } from '@inquirer/core';
const input = createPrompt((config, done) => {
// Implement logic
return '? My question';
});
// 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);
}
});
// ...
```
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);
});
// ...
```
### Ref hook
Refs let a prompt hold some information that isnt 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.
`useRef` declares a ref. You can hold any value in it, but most often its used to hold a timeout ID.
```ts
const input = createPrompt((config, done) => {
const timeout = useRef(null);
// ...
```
### Effect Hook
Effects let a prompt connect to and synchronize with external systems. This includes dealing with network or animations.
`useEffect` connects a component to an external system.
```ts
const chat = createPrompt((config, done) => {
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]);
// ...
```
### Performance hook
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.
`useMemo` lets you cache the result of an expensive calculation.
```ts
const todoSelect = createPrompt((config, done) => {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
```
### Rendering hooks
#### Prefix / loading
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.
`usePrefix` is a built-in hook to do this.
```ts
const input = createPrompt((config, done) => {
const prefix = usePrefix({ status });
return `${prefix} My question`;
});
```
#### Pagination
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.
Pagination works by taking in the list of options and returning a subset of the rendered items that fit within the page. The hook takes in a few options. It needs a list of options (`items`), and a `pageSize` which is the number of lines to be rendered. The `active` index is the index of the currently selected/selectable item. The `loop` option is a boolean that indicates if the list should loop around when reaching the end: this is the default behavior. The pagination hook renders items only as necessary, so it takes a function that can render an item at an index, including an `active` state, called `renderItem`.
```js
export default createPrompt((config, done) => {
const [active, setActive] = useState(0);
const allChoices = config.choices.map((choice) => choice.name);
const page = usePagination({
items: allChoices,
active: active,
renderItem: ({ item, index, isActive }) => `${isActive ? ">" : " "}${index}. ${item.toString()}`
pageSize: config.pageSize,
loop: config.loop,
});
return `... ${page}`;
});
```
## `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.
To allow standard customization:
```ts
import { createPrompt, usePrefix, makeTheme, type Theme } from '@inquirer/core';
import type { PartialDeep } from '@inquirer/type';
type PromptConfig = {
theme?: PartialDeep<Theme>;
};
export default createPrompt<string, PromptConfig>((config, done) => {
const theme = makeTheme(config.theme);
const prefix = usePrefix({ status, theme });
return `${prefix} ${theme.style.highlight('hello')}`;
});
```
To setup a custom theme:
```ts
import { createPrompt, makeTheme, type Theme } from '@inquirer/core';
import type { PartialDeep } from '@inquirer/type';
type PromptTheme = {};
const promptTheme: PromptTheme = {
icon: '!',
};
type PromptConfig = {
theme?: PartialDeep<Theme<PromptTheme>>;
};
export default createPrompt<string, PromptConfig>((config, done) => {
const theme = makeTheme(promptTheme, config.theme);
const prefix = usePrefix({ status, theme });
return `${prefix} ${theme.icon}`;
});
```
The [default theme keys cover](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/core/src/lib/theme.ts):
```ts
type DefaultTheme = {
prefix: string | { idle: string; done: string };
spinner: {
interval: number;
frames: string[];
};
style: {
answer: (text: string) => string;
message: (text: string, status: 'idle' | 'done' | 'loading') => string;
error: (text: string) => string;
defaultAnswer: (text: string) => string;
help: (text: string) => string;
highlight: (text: string) => string;
key: (text: string) => string;
};
};
```
# 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.ts)
- [Input Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/input/src/index.ts)
- [Password Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/password/src/index.ts)
- [Editor Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/editor/src/index.ts)
- [Select Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/select/src/index.ts)
- [Checkbox Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/checkbox/src/index.ts)
- [Rawlist Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/rawlist/src/index.ts)
- [Expand Prompt](https://github.com/SBoudrias/Inquirer.js/blob/main/packages/expand/src/index.ts)
```ts
import colors from 'yoctocolors';
import {
createPrompt,
useState,
useKeypress,
isEnterKey,
usePrefix,
type Status,
} from '@inquirer/core';
const confirm = createPrompt<boolean, { message: string; default?: boolean }>(
(config, done) => {
const [status, setStatus] = useState<Status>('idle');
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
Copyright (c) 2023 Simon Boudrias (twitter: [@vaxilart](https://twitter.com/Vaxilart))<br/>
Licensed under the MIT license.

View File

@@ -0,0 +1,13 @@
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';

View File

@@ -0,0 +1,46 @@
"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; } });

View File

@@ -0,0 +1,10 @@
/**
* 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;
}

View File

@@ -0,0 +1,28 @@
"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;

View File

@@ -0,0 +1,4 @@
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 {};

View File

@@ -0,0 +1,156 @@
"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;
}

View File

@@ -0,0 +1,20 @@
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;
}

View File

@@ -0,0 +1,29 @@
"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;

View File

@@ -0,0 +1,23 @@
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 {};

View File

@@ -0,0 +1,118 @@
"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;
},
};

View File

@@ -0,0 +1,12 @@
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;

View File

@@ -0,0 +1,29 @@
"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;

View File

@@ -0,0 +1,3 @@
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>>;

View File

@@ -0,0 +1,33 @@
"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);
}

View File

@@ -0,0 +1,16 @@
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;

View File

@@ -0,0 +1,124 @@
"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');
}

View File

@@ -0,0 +1,7 @@
export declare class PromisePolyfill<T> extends Promise<T> {
static withResolver<T>(): {
promise: Promise<T>;
resolve: (value: T) => void;
reject: (error: unknown) => void;
};
}

View File

@@ -0,0 +1,18 @@
"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;

View File

@@ -0,0 +1,14 @@
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;
}

View File

@@ -0,0 +1,82 @@
"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;

View File

@@ -0,0 +1,155 @@
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 {};

View File

@@ -0,0 +1,27 @@
"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}>`)),
},
};

View File

@@ -0,0 +1,2 @@
import type { InquirerReadline } from '@inquirer/type';
export declare function useEffect(cb: (rl: InquirerReadline) => void | (() => void), depArray: ReadonlyArray<unknown>): void;

View File

@@ -0,0 +1,14 @@
"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);
});
}

View File

@@ -0,0 +1,3 @@
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;

View File

@@ -0,0 +1,23 @@
"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);
};
}, []);
}

View File

@@ -0,0 +1 @@
export declare function useMemo<Value>(fn: () => Value, dependencies: ReadonlyArray<unknown>): Value;

View File

@@ -0,0 +1,17 @@
"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;
});
}

View File

@@ -0,0 +1,5 @@
import type { Theme, Status } from './theme.ts';
export declare function usePrefix({ status, theme, }: {
status?: Status;
theme?: Theme;
}): string;

View File

@@ -0,0 +1,38 @@
"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']);
}

View File

@@ -0,0 +1,6 @@
export declare function useRef<Value>(val: Value): {
current: Value;
};
export declare function useRef<Value>(val?: Value): {
current: Value | undefined;
};

View File

@@ -0,0 +1,7 @@
"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];
}

View File

@@ -0,0 +1,4 @@
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 {};

View File

@@ -0,0 +1,23 @@
"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];
});
}

View File

@@ -0,0 +1,13 @@
/**
* 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;

View File

@@ -0,0 +1,32 @@
"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 });
}

View File

@@ -0,0 +1,3 @@
{
"type": "commonjs"
}

View File

@@ -0,0 +1,13 @@
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';

View File

@@ -0,0 +1,12 @@
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";

View File

@@ -0,0 +1,10 @@
/**
* 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;
}

View File

@@ -0,0 +1,21 @@
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');
}
}

View File

@@ -0,0 +1,4 @@
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 {};

View File

@@ -0,0 +1,117 @@
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;
}

View File

@@ -0,0 +1,20 @@
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;
}

View File

@@ -0,0 +1,21 @@
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';
}

View File

@@ -0,0 +1,23 @@
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 {};

View File

@@ -0,0 +1,110 @@
/* 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;
},
};

View File

@@ -0,0 +1,12 @@
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;

View File

@@ -0,0 +1,19 @@
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';

View File

@@ -0,0 +1,3 @@
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>>;

View File

@@ -0,0 +1,30 @@
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);
}

View File

@@ -0,0 +1,16 @@
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;

View File

@@ -0,0 +1,121 @@
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');
}

View File

@@ -0,0 +1,7 @@
export declare class PromisePolyfill<T> extends Promise<T> {
static withResolver<T>(): {
promise: Promise<T>;
resolve: (value: T) => void;
reject: (error: unknown) => void;
};
}

View File

@@ -0,0 +1,14 @@
// 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 };
}
}

View File

@@ -0,0 +1,14 @@
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;
}

View File

@@ -0,0 +1,79 @@
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();
}
}

View File

@@ -0,0 +1,155 @@
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 {};

View File

@@ -0,0 +1,21 @@
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}>`)),
},
};

View File

@@ -0,0 +1,2 @@
import type { InquirerReadline } from '@inquirer/type';
export declare function useEffect(cb: (rl: InquirerReadline) => void | (() => void), depArray: ReadonlyArray<unknown>): void;

View File

@@ -0,0 +1,11 @@
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);
});
}

View File

@@ -0,0 +1,3 @@
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;

View File

@@ -0,0 +1,20 @@
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);
};
}, []);
}

View File

@@ -0,0 +1 @@
export declare function useMemo<Value>(fn: () => Value, dependencies: ReadonlyArray<unknown>): Value;

View File

@@ -0,0 +1,14 @@
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;
});
}

View File

@@ -0,0 +1,5 @@
import type { Theme, Status } from './theme.ts';
export declare function usePrefix({ status, theme, }: {
status?: Status;
theme?: Theme;
}): string;

View File

@@ -0,0 +1,35 @@
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']);
}

Some files were not shown because too many files have changed in this diff Show More