@reliverse/rempts-utils 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # rempts-utils
2
+
3
+ Utility functions for building CLI applications with Rempts.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add rempts-utils
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - 🎨 **Colors** - Terminal colors and styling
14
+ - 💬 **Prompts** - Interactive prompts and confirmations
15
+ - ⏳ **Spinners** - Loading indicators
16
+ - 📋 **Formatting** - Tables, lists, and text formatting
17
+ - 🔍 **Validation** - Input validation helpers
18
+
19
+ ## Usage
20
+
21
+ ### Colors
22
+
23
+ Style your terminal output:
24
+
25
+ ```typescript
26
+ import { colors } from '@reliverse/rempts-utils'
27
+
28
+ console.log(relico.green('✓ Success!'))
29
+ console.log(relico.red('✗ Error!'))
30
+ console.log(relico.blue('ℹ Info'))
31
+ console.log(relico.yellow('⚠ Warning'))
32
+ console.log(relico.bold('Bold text'))
33
+ console.log(relico.dim('Dimmed text'))
34
+ ```
35
+
36
+ ### Prompts
37
+
38
+ Interactive user input:
39
+
40
+ ```typescript
41
+ import { prompt, confirm, select } from '@reliverse/rempts-utils'
42
+
43
+ // Text input
44
+ const name = await prompt({
45
+ message: 'What is your name?',
46
+ default: 'Anonymous',
47
+ validate: (value) => value.length > 0
48
+ })
49
+
50
+ // Confirmation
51
+ const proceed = await confirm({
52
+ message: 'Do you want to continue?',
53
+ default: true
54
+ })
55
+
56
+ // Selection
57
+ const choice = await select({
58
+ message: 'Choose your favorite framework',
59
+ options: [
60
+ { label: 'Bun', value: 'bun' },
61
+ { label: 'Node.js', value: 'node' },
62
+ { label: 'Deno', value: 'deno' }
63
+ ]
64
+ })
65
+ ```
66
+
67
+ ### Spinners
68
+
69
+ Show progress for long-running tasks:
70
+
71
+ ```typescript
72
+ import { spinner } from '@reliverse/rempts-utils'
73
+
74
+ const spin = spinner()
75
+ spin.start('Loading...')
76
+
77
+ // Do some work
78
+ await someAsyncTask()
79
+
80
+ spin.success('Done!')
81
+ // or
82
+ spin.error('Failed!')
83
+ // or
84
+ spin.stop()
85
+ ```
86
+
87
+ ### Tables
88
+
89
+ Display structured data:
90
+
91
+ ```typescript
92
+ import { table } from '@reliverse/rempts-utils'
93
+
94
+ const data = [
95
+ { name: 'John', age: 30, city: 'New York' },
96
+ { name: 'Jane', age: 25, city: 'London' },
97
+ { name: 'Bob', age: 35, city: 'Paris' }
98
+ ]
99
+
100
+ console.log(table(data))
101
+ ```
102
+
103
+ ### Lists
104
+
105
+ Format lists with bullets or numbers:
106
+
107
+ ```typescript
108
+ import { list } from '@reliverse/rempts-utils'
109
+
110
+ // Bullet list
111
+ console.log(list([
112
+ 'First item',
113
+ 'Second item',
114
+ 'Third item'
115
+ ]))
116
+
117
+ // Numbered list
118
+ console.log(list([
119
+ 'First step',
120
+ 'Second step',
121
+ 'Third step'
122
+ ], { ordered: true }))
123
+ ```
124
+
125
+ ## API Reference
126
+
127
+ ### Colors
128
+
129
+ - `relico.red(text)` - Red text
130
+ - `relico.green(text)` - Green text
131
+ - `relico.blue(text)` - Blue text
132
+ - `relico.yellow(text)` - Yellow text
133
+ - `relico.cyan(text)` - Cyan text
134
+ - `relico.magenta(text)` - Magenta text
135
+ - `relico.bold(text)` - Bold text
136
+ - `relico.dim(text)` - Dimmed text
137
+ - `relico.underline(text)` - Underlined text
138
+
139
+ ### Prompts
140
+
141
+ - `prompt(options)` - Text input prompt
142
+ - `confirm(options)` - Yes/no confirmation
143
+ - `select(options)` - Single selection from list
144
+ - `multiselect(options)` - Multiple selection from list
145
+
146
+ ### Spinners
147
+
148
+ - `spinner(options)` - Create a new spinner instance
149
+ - `spinner.start(message)` - Start spinning with message
150
+ - `spinner.success(message)` - Stop with success message
151
+ - `spinner.error(message)` - Stop with error message
152
+ - `spinner.stop()` - Stop spinning
153
+
154
+ ### Formatting
155
+
156
+ - `table(data, options)` - Format data as table
157
+ - `list(items, options)` - Format items as list
158
+ - `tree(data, options)` - Format hierarchical data as tree
159
+
160
+ ## License
161
+
162
+ MIT © blefnk
package/dist/mod.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { RemptsUtils } from "./types.js";
2
+ export * from "./types.js";
3
+ export declare const utils: RemptsUtils;
4
+ export { createSpinner as spinner } from "./spinner.js";
5
+ export declare const prompt: RemptsUtils["prompt"];
6
+ export { getDotPath, SchemaError } from "@standard-schema/utils";
7
+ export { confirm, password, select } from "./prompt.js";
8
+ export { validate, validateFields } from "./validation.js";
package/dist/mod.js ADDED
@@ -0,0 +1,46 @@
1
+ import { confirm, password, prompt as promptFn, select } from "./prompt.js";
2
+ import { createSpinner } from "./spinner.js";
3
+ export * from "./types.js";
4
+ export const utils = {
5
+ prompt: Object.assign(promptFn, {
6
+ confirm,
7
+ select,
8
+ password,
9
+ text: (message, options) => promptFn(message, options),
10
+ multiselect: async (message, options) => {
11
+ const { options: choices } = options;
12
+ const selected = [];
13
+ console.log(message);
14
+ for (const choice of choices) {
15
+ const ok = await confirm(`Select ${choice.label}?`, { default: false });
16
+ if (ok) {
17
+ selected.push(choice.value);
18
+ }
19
+ }
20
+ return selected;
21
+ }
22
+ }),
23
+ spinner: createSpinner
24
+ };
25
+ export { createSpinner as spinner } from "./spinner.js";
26
+ export const prompt = Object.assign(promptFn, {
27
+ confirm,
28
+ select,
29
+ password,
30
+ text: (message, options) => promptFn(message, options),
31
+ multiselect: async (message, options) => {
32
+ const { options: choices } = options;
33
+ const selected = [];
34
+ console.log(message);
35
+ for (const choice of choices) {
36
+ const ok = await confirm(`Select ${choice.label}?`, { default: false });
37
+ if (ok) {
38
+ selected.push(choice.value);
39
+ }
40
+ }
41
+ return selected;
42
+ }
43
+ });
44
+ export { getDotPath, SchemaError } from "@standard-schema/utils";
45
+ export { confirm, password, select } from "./prompt.js";
46
+ export { validate, validateFields } from "./validation.js";
@@ -0,0 +1,5 @@
1
+ import type { ConfirmOptions, PromptOptions, SelectOptions } from "./types.js";
2
+ export declare function prompt<T = string>(message: string, options?: PromptOptions): Promise<T>;
3
+ export declare function confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
4
+ export declare function select<T = string>(message: string, options: SelectOptions<T>): Promise<T>;
5
+ export declare function password<T = string>(message: string, options?: PromptOptions): Promise<T>;
package/dist/prompt.js ADDED
@@ -0,0 +1,172 @@
1
+ import { relico } from "@reliverse/relico";
2
+ const ESC = "\x1B";
3
+ const CSI = `${ESC}[`;
4
+ const CLEAR_LINE = `${CSI}2K`;
5
+ const CURSOR_START = `${CSI}G`;
6
+ const CURSOR_HIDE = `${CSI}?25l`;
7
+ const CURSOR_SHOW = `${CSI}?25h`;
8
+ async function readline(prompt2) {
9
+ process.stdout.write(prompt2);
10
+ for await (const line of console) {
11
+ return line;
12
+ }
13
+ return "";
14
+ }
15
+ export async function prompt(message, options = {}) {
16
+ const defaultHint = options.default ? ` (${options.default})` : "";
17
+ const promptText = `${message}${defaultHint} `;
18
+ while (true) {
19
+ const input = await readline(promptText);
20
+ const value = input.trim() || options.default || "";
21
+ if (options.schema) {
22
+ const result = await options.schema["~standard"].validate(value);
23
+ if (result.issues) {
24
+ console.error(relico.red("Invalid input:"));
25
+ for (const issue of result.issues) {
26
+ console.error(relico.dim(` \u2022 ${issue.message}`));
27
+ }
28
+ console.error();
29
+ continue;
30
+ }
31
+ return result.value;
32
+ }
33
+ if (options.validate) {
34
+ const result = options.validate(value);
35
+ if (result === true) {
36
+ return value;
37
+ }
38
+ if (typeof result === "string") {
39
+ console.error(`\u2717 ${result}`);
40
+ continue;
41
+ }
42
+ console.error("\u2717 Invalid input");
43
+ continue;
44
+ }
45
+ return value;
46
+ }
47
+ }
48
+ export async function confirm(message, options = {}) {
49
+ const defaultHint = options.default === true ? "Y/n" : options.default === false ? "y/N" : "y/n";
50
+ const promptText = `${message} (${defaultHint}) `;
51
+ while (true) {
52
+ const input = await readline(promptText);
53
+ const value = input.trim().toLowerCase();
54
+ if (!value && options.default !== void 0) {
55
+ return options.default;
56
+ }
57
+ if (value === "y" || value === "yes") {
58
+ return true;
59
+ }
60
+ if (value === "n" || value === "no") {
61
+ return false;
62
+ }
63
+ console.error("\u2717 Please answer with y/yes or n/no");
64
+ }
65
+ }
66
+ export async function select(message, options) {
67
+ const { options: choices, default: defaultValue } = options;
68
+ let selectedIndex = defaultValue ? choices.findIndex((opt) => opt.value === defaultValue) : 0;
69
+ if (selectedIndex === -1) {
70
+ selectedIndex = 0;
71
+ }
72
+ console.log(message);
73
+ process.stdout.write(CURSOR_HIDE);
74
+ drawOptions(choices, selectedIndex);
75
+ return new Promise((resolve) => {
76
+ process.stdin.setRawMode(true);
77
+ process.stdin.resume();
78
+ const cleanup = () => {
79
+ process.stdin.setRawMode(false);
80
+ process.stdin.pause();
81
+ process.stdout.write(CURSOR_SHOW);
82
+ };
83
+ process.stdin.on("data", (data) => {
84
+ const key = data.toString();
85
+ if (key === "\x1B[A") {
86
+ selectedIndex = Math.max(0, selectedIndex - 1);
87
+ drawOptions(choices, selectedIndex);
88
+ } else if (key === "\x1B[B") {
89
+ selectedIndex = Math.min(choices.length - 1, selectedIndex + 1);
90
+ drawOptions(choices, selectedIndex);
91
+ } else if (key === "\r" || key === "\n") {
92
+ cleanup();
93
+ for (let i = 0; i < choices.length; i++) {
94
+ process.stdout.write(`${CSI}1A${CLEAR_LINE}`);
95
+ }
96
+ const selected = choices[selectedIndex];
97
+ if (selected) {
98
+ console.log(`\u2713 ${selected.label}`);
99
+ resolve(selected.value);
100
+ }
101
+ } else if (key === "" || key === "\x1B") {
102
+ cleanup();
103
+ process.exit(0);
104
+ }
105
+ });
106
+ });
107
+ }
108
+ function drawOptions(options, selectedIndex) {
109
+ for (let i = 0; i < options.length; i++) {
110
+ process.stdout.write(`${CSI}1A`);
111
+ }
112
+ options.forEach((option, index) => {
113
+ process.stdout.write(CLEAR_LINE + CURSOR_START);
114
+ const prefix = index === selectedIndex ? "\u276F " : " ";
115
+ const hint = option.hint ? ` (${option.hint})` : "";
116
+ console.log(`${prefix}${option.label}${hint}`);
117
+ });
118
+ }
119
+ export async function password(message, options = {}) {
120
+ process.stdout.write(`${message} `);
121
+ return new Promise((resolve) => {
122
+ let input = "";
123
+ process.stdin.setRawMode(true);
124
+ process.stdin.resume();
125
+ const cleanup = () => {
126
+ process.stdin.setRawMode(false);
127
+ process.stdin.pause();
128
+ console.log();
129
+ };
130
+ process.stdin.on("data", async (data) => {
131
+ const key = data.toString();
132
+ if (key === "\r" || key === "\n") {
133
+ cleanup();
134
+ if (options.schema) {
135
+ const result = await options.schema["~standard"].validate(input);
136
+ if (result.issues) {
137
+ console.error(relico.red("Invalid input:"));
138
+ for (const issue of result.issues) {
139
+ console.error(relico.dim(` \u2022 ${issue.message}`));
140
+ }
141
+ console.error();
142
+ password(message, options).then(resolve);
143
+ return;
144
+ }
145
+ resolve(result.value);
146
+ } else if (options.validate) {
147
+ const result = options.validate(input);
148
+ if (result === true) {
149
+ resolve(input);
150
+ } else {
151
+ const errorMsg = typeof result === "string" ? result : "Invalid input";
152
+ console.error(`\u2717 ${errorMsg}`);
153
+ password(message, options).then(resolve);
154
+ }
155
+ } else {
156
+ resolve(input);
157
+ }
158
+ } else if (key === "") {
159
+ cleanup();
160
+ process.exit(0);
161
+ } else if (key === "\x7F" || key === "\b") {
162
+ if (input.length > 0) {
163
+ input = input.slice(0, -1);
164
+ process.stdout.write("\b \b");
165
+ }
166
+ } else if (key.length === 1 && key >= " ") {
167
+ input += key;
168
+ process.stdout.write("*");
169
+ }
170
+ });
171
+ });
172
+ }
@@ -0,0 +1,2 @@
1
+ import type { Spinner, SpinnerOptions } from "./types.js";
2
+ export declare function createSpinner(options?: SpinnerOptions | string): Spinner;
@@ -0,0 +1,73 @@
1
+ const SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2
+ const CLEAR_LINE = "\x1B[2K";
3
+ const CURSOR_START = "\x1B[G";
4
+ export function createSpinner(options) {
5
+ const config = typeof options === "string" ? { text: options } : options || {};
6
+ let isSpinning = false;
7
+ let frameIndex = 0;
8
+ let intervalId = null;
9
+ let currentText = config.text || "";
10
+ const render = (symbol, text) => {
11
+ process.stdout.write(`${CLEAR_LINE}${CURSOR_START}${symbol} ${text}`);
12
+ };
13
+ const spinner = {
14
+ start(text) {
15
+ if (isSpinning) {
16
+ return;
17
+ }
18
+ isSpinning = true;
19
+ if (text !== void 0) {
20
+ currentText = text;
21
+ }
22
+ process.stdout.write("\x1B[?25l");
23
+ intervalId = setInterval(() => {
24
+ const frame = SPINNER_FRAMES[frameIndex];
25
+ render(frame, currentText);
26
+ frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
27
+ }, 80);
28
+ },
29
+ stop(text) {
30
+ if (!isSpinning) {
31
+ return;
32
+ }
33
+ isSpinning = false;
34
+ if (intervalId) {
35
+ clearInterval(intervalId);
36
+ intervalId = null;
37
+ }
38
+ process.stdout.write(CLEAR_LINE + CURSOR_START);
39
+ process.stdout.write("\x1B[?25h");
40
+ if (text) {
41
+ console.log(text);
42
+ }
43
+ },
44
+ succeed(text) {
45
+ this.stop();
46
+ console.log(`\u2705 ${text || currentText}`);
47
+ },
48
+ fail(text) {
49
+ this.stop();
50
+ console.log(`\u274C ${text || currentText}`);
51
+ },
52
+ warn(text) {
53
+ this.stop();
54
+ console.log(`\u26A0\uFE0F ${text || currentText}`);
55
+ },
56
+ info(text) {
57
+ this.stop();
58
+ console.log(`\u2139\uFE0F ${text || currentText}`);
59
+ },
60
+ update(text) {
61
+ currentText = text;
62
+ if (isSpinning) {
63
+ render(SPINNER_FRAMES[frameIndex], currentText);
64
+ }
65
+ }
66
+ };
67
+ process.on("exit", () => spinner.stop());
68
+ process.on("SIGINT", () => {
69
+ spinner.stop();
70
+ process.exit(0);
71
+ });
72
+ return spinner;
73
+ }
@@ -0,0 +1,82 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+ export interface PromptOptions {
3
+ default?: string;
4
+ validate?: (input: string) => boolean | string;
5
+ schema?: StandardSchemaV1;
6
+ placeholder?: string;
7
+ multiline?: boolean;
8
+ }
9
+ export interface ConfirmOptions {
10
+ default?: boolean;
11
+ }
12
+ export interface SelectOption<T = string> {
13
+ label: string;
14
+ value: T;
15
+ hint?: string;
16
+ }
17
+ export interface SelectOptions<T = string> {
18
+ options: SelectOption<T>[];
19
+ default?: T;
20
+ hint?: string;
21
+ }
22
+ export interface MultiSelectOptions<T = string> extends SelectOptions<T> {
23
+ min?: number;
24
+ max?: number;
25
+ }
26
+ export interface SpinnerOptions {
27
+ text?: string;
28
+ color?: string;
29
+ }
30
+ export interface Spinner {
31
+ start(text?: string): void;
32
+ stop(text?: string): void;
33
+ succeed(text?: string): void;
34
+ fail(text?: string): void;
35
+ warn(text?: string): void;
36
+ info(text?: string): void;
37
+ update(text: string): void;
38
+ }
39
+ export type ColorFunction = (text: string) => string;
40
+ export interface Colors {
41
+ black: ColorFunction;
42
+ red: ColorFunction;
43
+ green: ColorFunction;
44
+ yellow: ColorFunction;
45
+ blue: ColorFunction;
46
+ magenta: ColorFunction;
47
+ cyan: ColorFunction;
48
+ white: ColorFunction;
49
+ gray: ColorFunction;
50
+ brightRed: ColorFunction;
51
+ brightGreen: ColorFunction;
52
+ brightYellow: ColorFunction;
53
+ brightBlue: ColorFunction;
54
+ brightMagenta: ColorFunction;
55
+ brightCyan: ColorFunction;
56
+ brightWhite: ColorFunction;
57
+ bgRed: ColorFunction;
58
+ bgGreen: ColorFunction;
59
+ bgYellow: ColorFunction;
60
+ bgBlue: ColorFunction;
61
+ bgMagenta: ColorFunction;
62
+ bgCyan: ColorFunction;
63
+ bgWhite: ColorFunction;
64
+ bold: ColorFunction;
65
+ dim: ColorFunction;
66
+ italic: ColorFunction;
67
+ underline: ColorFunction;
68
+ strikethrough: ColorFunction;
69
+ reset: ColorFunction;
70
+ strip: (text: string) => string;
71
+ }
72
+ export interface RemptsUtils {
73
+ prompt: {
74
+ <T = string>(message: string, options?: PromptOptions): Promise<T>;
75
+ confirm(message: string, options?: ConfirmOptions): Promise<boolean>;
76
+ select<T = string>(message: string, options: SelectOptions<T>): Promise<T>;
77
+ password<T = string>(message: string, options?: PromptOptions): Promise<T>;
78
+ text(message: string, options?: PromptOptions): Promise<string>;
79
+ multiselect<T = string>(message: string, options: MultiSelectOptions<T>): Promise<T[]>;
80
+ };
81
+ spinner: (options?: SpinnerOptions | string) => Spinner;
82
+ }
package/dist/types.js ADDED
File without changes
@@ -0,0 +1,13 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+ /**
3
+ * Validate a value against a schema and throw SchemaError on failure
4
+ */
5
+ export declare function validate<TSchema extends StandardSchemaV1>(schema: TSchema, value: unknown): Promise<StandardSchemaV1.InferOutput<TSchema>>;
6
+ /**
7
+ * Validate multiple fields and return aggregated errors
8
+ */
9
+ export declare function validateFields<T extends Record<string, StandardSchemaV1>>(schemas: T, values: Record<string, unknown>): Promise<{
10
+ [K in keyof T]: StandardSchemaV1.InferOutput<T[K]>;
11
+ } | {
12
+ errors: Record<string, string[]>;
13
+ }>;
@@ -0,0 +1,24 @@
1
+ import { SchemaError } from "@standard-schema/utils";
2
+ export async function validate(schema, value) {
3
+ const result = await schema["~standard"].validate(value);
4
+ if (result.issues) {
5
+ throw new SchemaError(result.issues);
6
+ }
7
+ return result.value;
8
+ }
9
+ export async function validateFields(schemas, values) {
10
+ const results = {};
11
+ const errors = {};
12
+ for (const [field, schema] of Object.entries(schemas)) {
13
+ const result = await schema["~standard"].validate(values[field]);
14
+ if (result.issues) {
15
+ errors[field] = result.issues.map((issue) => issue.message);
16
+ } else {
17
+ results[field] = result.value;
18
+ }
19
+ }
20
+ if (Object.keys(errors).length > 0) {
21
+ return { errors };
22
+ }
23
+ return results;
24
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@reliverse/rempts-utils",
3
+ "version": "2.3.1",
4
+ "description": "Built-in utilities for Rempts CLI framework - prompts, spinners, and colors",
5
+ "keywords": [
6
+ "cli",
7
+ "prompts",
8
+ "rempts",
9
+ "spinner",
10
+ "terminal",
11
+ "utilities"
12
+ ],
13
+ "homepage": "https://github.com/reliverse/dler#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/reliverse/dler/issues"
16
+ },
17
+ "license": "MIT",
18
+ "author": "blefnk",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/reliverse/dler.git",
22
+ "directory": "packages/utils"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "package.json",
27
+ "README.md"
28
+ ],
29
+ "type": "module",
30
+ "module": "./src/mod.ts",
31
+ "types": "./src/mod.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/mod.d.ts",
35
+ "import": "./dist/mod.js"
36
+ }
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "dependencies": {
42
+ "@reliverse/relico": "2.3.1",
43
+ "@standard-schema/spec": "^1.1.0",
44
+ "@standard-schema/utils": "^0.3.0",
45
+ "arktype": "^2.1.29"
46
+ },
47
+ "engines": {
48
+ "bun": ">=1.3.5"
49
+ }
50
+ }