@reliverse/relinka 1.0.0

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.
Files changed (63) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +141 -0
  3. package/output/components/animate.d.ts +13 -0
  4. package/output/components/animate.js +55 -0
  5. package/output/components/any-key.d.ts +7 -0
  6. package/output/components/any-key.js +56 -0
  7. package/output/components/ascii-art.d.ts +6 -0
  8. package/output/components/ascii-art.js +12 -0
  9. package/output/components/confirm.d.ts +3 -0
  10. package/output/components/confirm.js +99 -0
  11. package/output/components/date.d.ts +6 -0
  12. package/output/components/date.js +125 -0
  13. package/output/components/end.d.ts +2 -0
  14. package/output/components/end.js +36 -0
  15. package/output/components/mono.d.ts +5 -0
  16. package/output/components/mono.js +64 -0
  17. package/output/components/next-steps.d.ts +2 -0
  18. package/output/components/next-steps.js +25 -0
  19. package/output/components/num-multi-select.d.ts +3 -0
  20. package/output/components/num-multi-select.js +108 -0
  21. package/output/components/num-select.d.ts +5 -0
  22. package/output/components/num-select.js +122 -0
  23. package/output/components/number.d.ts +3 -0
  24. package/output/components/number.js +92 -0
  25. package/output/components/password.d.ts +3 -0
  26. package/output/components/password.js +117 -0
  27. package/output/components/results.d.ts +10 -0
  28. package/output/components/results.js +34 -0
  29. package/output/components/select.d.ts +2 -0
  30. package/output/components/select.js +97 -0
  31. package/output/components/spinner.d.ts +15 -0
  32. package/output/components/spinner.js +110 -0
  33. package/output/components/start.d.ts +2 -0
  34. package/output/components/start.js +33 -0
  35. package/output/components/text.d.ts +3 -0
  36. package/output/components/text.js +88 -0
  37. package/output/hooks/useKeyPress.d.ts +4 -0
  38. package/output/hooks/useKeyPress.js +14 -0
  39. package/output/main.d.ts +16 -0
  40. package/output/main.js +16 -0
  41. package/output/types/prod.d.ts +83 -0
  42. package/output/types/prod.js +0 -0
  43. package/output/utils/colorize.d.ts +2 -0
  44. package/output/utils/colorize.js +129 -0
  45. package/output/utils/errors.d.ts +1 -0
  46. package/output/utils/errors.js +11 -0
  47. package/output/utils/mapping.d.ts +4 -0
  48. package/output/utils/mapping.js +49 -0
  49. package/output/utils/messages.d.ts +17 -0
  50. package/output/utils/messages.js +203 -0
  51. package/output/utils/platforms.d.ts +1 -0
  52. package/output/utils/platforms.js +22 -0
  53. package/output/utils/prompt-tmp.d.ts +13 -0
  54. package/output/utils/prompt-tmp.js +254 -0
  55. package/output/utils/prompt.d.ts +13 -0
  56. package/output/utils/prompt.js +254 -0
  57. package/output/utils/readline.d.ts +2 -0
  58. package/output/utils/readline.js +9 -0
  59. package/output/utils/terminal.d.ts +5 -0
  60. package/output/utils/terminal.js +33 -0
  61. package/output/utils/variants.d.ts +9 -0
  62. package/output/utils/variants.js +49 -0
  63. package/package.json +96 -0
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2024 Nazarii Korniienko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # @reliverse/relinka
2
+
3
+ [**Docs**](https://docs.reliverse.org/relinka) | [**npmjs.com**](https://npmjs.com/package/@reliverse/relinka) | [**GitHub**](https://github.com/reliverse/relinka)
4
+
5
+ [![Separator](./public/split.png)](https://docs.reliverse.org/relinka)
6
+
7
+ @reliverse/relinka is a powerful library that enables seamless, type-safe, and resilient prompts for command-line applications. Crafted with simplicity and elegance, it provides developers with an intuitive and robust way to build interactive CLIs.
8
+
9
+ [![CLI Example](./public/example.png)](https://docs.reliverse.org/relinka)
10
+
11
+ ## Installation
12
+
13
+ Install with your preferred package manager:
14
+
15
+ ```sh
16
+ bun add @reliverse/relinka # Replace 'bun' with npm, pnpm, or yarn if desired (deno and jsr support coming soon)
17
+ ```
18
+
19
+ ## Key Features
20
+
21
+ - **Type Safety**: Built with TypeScript, ensuring strong typing to prevent runtime errors.
22
+ - **Schema Validation**: Validates user inputs using schemas for enhanced reliability.
23
+ - **Flexible Prompt Types**: Supports a range of prompt types, including text, password, number, select, and multiselect.
24
+ - **Crash Resilience**: Designed to handle cancellations and errors gracefully, ensuring stability.
25
+
26
+ [![Confirm Prompt](./public/confirm.gif)](https://docs.reliverse.org/relinka)
27
+
28
+ ## Prompt Types
29
+
30
+ Each type has its own validation and display logic. More types are planned for future releases.
31
+
32
+ - **Text**: Collects text input.
33
+ - **Password**: Hidden input for secure password entries.
34
+ - **Number**: Numeric input with optional validation.
35
+ - **Confirm**: Simple Yes/No prompt.
36
+ - **Select**: Dropdown selection for multiple choices.
37
+ - **Multiselect**: Allows users to select multiple items from a list.
38
+
39
+ [![Multiselect Prompt](./public/list.gif)](https://docs.reliverse.org/relinka)
40
+
41
+ ## Input Validation
42
+
43
+ All prompts support custom validation logic, providing immediate feedback to users.
44
+
45
+ [![Number Prompt with Validation](./public/validate.gif)](https://docs.reliverse.org/relinka)
46
+
47
+ ## Contributing
48
+
49
+ @reliverse/relinka is a work in progress. We welcome feedback and contributions to help make it the best library it can be. Thank you!
50
+
51
+ Here is how to install the library for development:
52
+
53
+ ```sh
54
+ git clone https://github.com/reliverse/relinka.git
55
+ cd relinka
56
+ bun i
57
+ ```
58
+
59
+ ## Playground
60
+
61
+ Run `bun dev` to launch the [examples/run-example.ts](./examples/run-example.ts) CLI, where you can dive into and explore any of the examples listed below. Experiment with @reliverse/relinka by running examples locally or reviewing the linked code:
62
+
63
+ 1. **[1-main-example.ts](./examples/1-main-example.ts)**: A comprehensive example of a CLI application featuring a well styled UI config. This example showcases all available prompt components, with code organized into separate functions and files for better readability and clarity.
64
+ 2. **[2-mono-example.ts](./examples/2-mono-example.ts)**: A quiz game example inspired by Fireship's [video](https://youtube.com/watch?v=_oHByo8tiEY). It demonstrates the dynamic capabilities of @reliverse/relinka by using a prompt() that includes all prompt components, so you don't need to import each component separately.
65
+ 3. **[3-basic-example.ts](./examples/3-basic-example.ts)**: A simple example highlighting the core functionalities of @reliverse/relinka. The entire implementation is contained within a single file for easy understanding.
66
+
67
+ ## Prompts Library Comparison
68
+
69
+ > **Note:** This table contains approximate and placeholder values. More detailed assessments will be provided as libraries continue to evolve.
70
+
71
+ **Icon Legend:**
72
+
73
+ - 🟡: Not yet verified
74
+ - 🟢: Fully supported
75
+ - 🔵: Partially supported
76
+ - 🔴: Not supported
77
+
78
+ | **Feature** | **@reliverse/relinka** | **@inquirer/prompts** | **@clack/prompts** | **@terkelg/prompts** | **@cronvel/terminal-kit** | **@unjs/consola** |
79
+ |-----------------------------------------------|----------------------------------------------------|------------------------|---------------------|------------------------------|-----------------------------|-------------------|
80
+ | **Full Node.js Modules Support** | 🟢 ESM-only | 🟡 | 🟡 | 🔴 CJS-only | 🔴 CJS-only | 🟡 |
81
+ | **Both terminal and browser support** | 🟢 | 🟡 | 🟡 | 🟡 | 🟡 | 🟢 |
82
+ | **Codebase typesafety with intellisense** | 🔵 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
83
+ | **Runtime typesafety with schema validation** | 🟢 TypeBox & Custom | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
84
+ | **Usage Examples** | 🟢 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
85
+ | **Mono Component** | 🟢 Mono (All-In-One) & Separate | 🟡 | 🟡 | 🟢 Mono-only | 🟡 | 🟡 |
86
+ | **Start Component** | 🟢 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
87
+ | **Text Component** | 🟢 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
88
+ | **Customization** | 🟢 Colors, typography, variants, borders, and more | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
89
+ | **Animated Text** | 🟢 Includes 6 animations | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
90
+ | **ASCII Art Component** | 🟢 Includes 290 fonts | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
91
+ | **Password Component** | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
92
+ | **Number Component** | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
93
+ | **Confirm Component** | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
94
+ | **Select Component** | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
95
+ | **Multiselect Component** | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
96
+ | **ProgressBar Component** | 🟡 | 🟡 | 🟡 | 🟡 | 🟢 | 🟡 |
97
+ | **Image Component** | 🟡 | 🟡 | 🟡 | 🟡 | 🟢 | 🟡 |
98
+ | **Custom Validation** | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
99
+ | **Error Handling** | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
100
+ | **Ease of Setup** | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
101
+ | **Crash Resilience** | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
102
+ | **General DX** | 🔵 Clean and understandable TypeScript code | 🟡 | 🟡 | 🔴 JS-only | 🔴 JS-only | 🟡 |
103
+ | **DX: Classes** | 🟢 Minimal number of classes as possible | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
104
+ | **Documentation** | 🟡 | 🟡 | 🟡 | 🔵 | 🟢 | 🟡 |
105
+ | **Designed With UX in Mind** | 🔵 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 |
106
+
107
+ **Related Links**: [ESM/CJS](https://dev.to/iggredible/what-the-heck-are-cjs-amd-umd-and-esm-ikm), ["Pure ESM package"](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c), [Clean code](https://github.com/ryanmcdermott/clean-code-javascript#readme), ["UX patterns for CLI tools"](https://lucasfcosta.com/2022/06/01/ux-patterns-cli-tools.html), [DX (Developer Experience)](https://github.blog/enterprise-software/collaboration/developer-experience-what-is-it-and-why-should-you-care), [TypeBox](https://github.com/sinclairzx81/typebox#readme), ["ANSI Escape Sequences"](https://gist.github.com/ConnerWill/d4b6c776b509add763e17f9f113fd25b)
108
+
109
+ ## Special Thanks
110
+
111
+ This project wouldn’t exist without the amazing work of the following projects:
112
+
113
+ [@inquirer/prompts](https://github.com/SBoudrias/Inquirer.js#readme) | [terkelg/prompts](https://github.com/lu-jiejie/prompts-plus#readme) | [@clack/prompts](https://github.com/bombshell-dev/clack#readme) | [create-t3-app](https://github.com/t3-oss/create-t3-app#readme) | [create-astro](https://github.com/withastro/astro/tree/main/packages/create-astro#readme) | [cronvel/terminal-kit](https://github.com/cronvel/terminal-kit#readme) | [unjs/consola](https://github.com/unjs/consola#readme)
114
+
115
+ ## Wrap-Up
116
+
117
+ @reliverse/relinka is a versatile library designed to accelerate CLI development by providing customizable prompt components. Integrated into the [Reliverse CLI](https://github.com/blefnk/reliverse#readme), @reliverse/relinka enables you to create a unique design aligned with your CLI app’s aesthetics, similar to how @shadcn/ui supports customizable web UI components. Quickly get started by copying configurations from the [Reliverse Docs](https://docs.reliverse.org/relinka) and using components that fit your project, making it faster to bring your CLI app to life. You’re free to customize each component as desired, with default designs provided to ensure an attractive interface from the start.
118
+
119
+ **Example Configuration:**
120
+
121
+ ```typescript
122
+ const basicConfig = {
123
+ titleColor: "cyanBright",
124
+ titleTypography: "bold",
125
+ borderColor: "viceGradient",
126
+ } satisfies OptionalPromptOptions;
127
+
128
+ const extendedConfig = {
129
+ ...basicConfig,
130
+ contentTypography: "italic",
131
+ contentColor: "dim",
132
+ } satisfies OptionalPromptOptions;
133
+
134
+ const username = await textPrompt({
135
+ id: "username",
136
+ title: "We're glad you're testing our library!",
137
+ content: "Let's get to know each other!\nWhat's your username?",
138
+ schema: schema.properties.username,
139
+ ...extendedConfig,
140
+ });
141
+ ```
@@ -0,0 +1,13 @@
1
+ import { type Animation, type AnimationName } from "@figliolia/chalk-animation";
2
+ import type { ColorName, MsgType, TypographyName } from "../types/prod";
3
+ export declare const animationMap: Record<AnimationName, (text: string) => Animation>;
4
+ export declare function animateText({ title, anim, delay, type, titleColor, titleTypography, border, borderColor, }: {
5
+ title: string;
6
+ anim: AnimationName;
7
+ delay?: number;
8
+ type?: MsgType;
9
+ titleColor?: ColorName;
10
+ titleTypography?: TypographyName;
11
+ borderColor?: ColorName;
12
+ border?: boolean;
13
+ }): Promise<void>;
@@ -0,0 +1,55 @@
1
+ import {
2
+ ChalkAnimation
3
+ } from "@figliolia/chalk-animation";
4
+ import { deleteLastLine } from "../utils/terminal";
5
+ import { msg } from "../utils/messages.js";
6
+ export const animationMap = {
7
+ rainbow: ChalkAnimation.rainbow,
8
+ pulse: ChalkAnimation.pulse,
9
+ glitch: ChalkAnimation.glitch,
10
+ radar: ChalkAnimation.radar,
11
+ neon: ChalkAnimation.neon,
12
+ karaoke: ChalkAnimation.karaoke
13
+ };
14
+ function calculateDelay(text) {
15
+ const baseDelay = 1e3;
16
+ const delayPerCharacter = 50;
17
+ return baseDelay + text.length * delayPerCharacter;
18
+ }
19
+ export async function animateText({
20
+ title,
21
+ anim,
22
+ delay,
23
+ type = "M_INFO",
24
+ titleColor = "cyanBright",
25
+ titleTypography = "bold",
26
+ border = true,
27
+ borderColor = "viceGradient"
28
+ }) {
29
+ const finalDelay = delay ?? calculateDelay(title);
30
+ const animation = animationMap[anim](title);
31
+ try {
32
+ await new Promise((resolve) => {
33
+ setTimeout(() => {
34
+ animation.stop();
35
+ deleteLastLine();
36
+ if (title.includes("\u2502 ")) {
37
+ title = title.replace("\u2502 ", "");
38
+ } else if (title.includes("\u2139 ")) {
39
+ title = title.replace("\u2139 ", "");
40
+ }
41
+ msg({
42
+ type,
43
+ title,
44
+ titleColor,
45
+ titleTypography,
46
+ borderColor,
47
+ border
48
+ });
49
+ resolve();
50
+ }, finalDelay);
51
+ });
52
+ } catch (error) {
53
+ console.error("Animation failed to complete.", error);
54
+ }
55
+ }
@@ -0,0 +1,7 @@
1
+ type Options = {
2
+ ctrlC?: number | "reject" | false;
3
+ preserveLog?: boolean;
4
+ hideMessage?: boolean;
5
+ };
6
+ export declare function pressAnyKeyPrompt(message?: string, options?: Options): Promise<void>;
7
+ export {};
@@ -0,0 +1,56 @@
1
+ import logUpdate from "log-update";
2
+ import { colorize } from "../utils/colorize";
3
+ import { restoreCursor } from "../utils/terminal";
4
+ const DEFAULT_MESSAGE = "Press any key to continue...";
5
+ const CTRL_C_CODE = 3;
6
+ export async function pressAnyKeyPrompt(message = DEFAULT_MESSAGE, options = {}) {
7
+ const { ctrlC = 1, preserveLog = false, hideMessage = false } = options;
8
+ if (message) {
9
+ message = `${colorize("\u2502", "viceGradient")} ${colorize(message, "dim")}`;
10
+ }
11
+ if (message && !hideMessage) {
12
+ logUpdate(message);
13
+ }
14
+ return new Promise((resolve, reject) => {
15
+ const cleanup = () => {
16
+ process.stdin.removeListener("data", handler);
17
+ if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
18
+ process.stdin.setRawMode(false);
19
+ }
20
+ process.stdin.pause();
21
+ restoreCursor();
22
+ };
23
+ const handleCtrlC = () => {
24
+ cleanup();
25
+ if (ctrlC === "reject") {
26
+ reject(new Error("User pressed CTRL+C"));
27
+ } else if (ctrlC === false) {
28
+ resolve();
29
+ } else if (typeof ctrlC === "number") {
30
+ process.exit(ctrlC);
31
+ } else {
32
+ throw new TypeError("Invalid ctrlC option");
33
+ }
34
+ };
35
+ const handler = (buffer) => {
36
+ cleanup();
37
+ if (message && !preserveLog) {
38
+ logUpdate.clear();
39
+ } else {
40
+ logUpdate.done();
41
+ process.stdout.write("\n");
42
+ }
43
+ const [firstByte] = buffer;
44
+ if (firstByte === CTRL_C_CODE) {
45
+ handleCtrlC();
46
+ } else {
47
+ resolve();
48
+ }
49
+ };
50
+ process.stdin.resume();
51
+ if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
52
+ process.stdin.setRawMode(true);
53
+ }
54
+ process.stdin.once("data", handler);
55
+ });
56
+ }
@@ -0,0 +1,6 @@
1
+ import { type Fonts } from "figlet";
2
+ export declare function createAsciiArt({ message, font, clearConsole, }: {
3
+ message: string;
4
+ font?: Fonts;
5
+ clearConsole?: boolean;
6
+ }): Promise<void>;
@@ -0,0 +1,12 @@
1
+ import { textSync } from "figlet";
2
+ export async function createAsciiArt({
3
+ message,
4
+ font = "Standard",
5
+ clearConsole = false
6
+ }) {
7
+ if (clearConsole) {
8
+ console.clear();
9
+ }
10
+ const asciiArt = textSync(message, { font });
11
+ console.log(asciiArt);
12
+ }
@@ -0,0 +1,3 @@
1
+ import type { TSchema, Static } from "@sinclair/typebox";
2
+ import type { PromptOptions } from "../types/prod";
3
+ export declare function confirmPrompt<T extends TSchema>(options: PromptOptions<T>): Promise<Static<T>>;
@@ -0,0 +1,99 @@
1
+ import { Value } from "@sinclair/typebox/value";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import readline from "node:readline/promises";
4
+ import { colorize } from "../utils/colorize";
5
+ import { bar, fmt, msg } from "../utils/messages";
6
+ import { countLines, deleteLastLines } from "../utils/terminal";
7
+ export async function confirmPrompt(options) {
8
+ const {
9
+ title,
10
+ defaultValue,
11
+ schema,
12
+ titleColor = "cyanBright",
13
+ answerColor = "none",
14
+ titleTypography = "bold",
15
+ titleVariant,
16
+ content,
17
+ contentColor,
18
+ contentTypography,
19
+ contentVariant,
20
+ borderColor = "viceGradient",
21
+ hintColor = "dim",
22
+ variantOptions,
23
+ action
24
+ } = options;
25
+ const rl = readline.createInterface({ input, output });
26
+ let linesToDelete = 0;
27
+ let errorMessage = "";
28
+ try {
29
+ while (true) {
30
+ if (linesToDelete > 0) {
31
+ deleteLastLines(linesToDelete);
32
+ }
33
+ const question = fmt({
34
+ type: errorMessage !== "" ? "M_ERROR" : "M_GENERAL",
35
+ title,
36
+ titleColor,
37
+ titleTypography,
38
+ titleVariant,
39
+ content,
40
+ contentColor,
41
+ contentTypography,
42
+ contentVariant,
43
+ borderColor,
44
+ variantOptions,
45
+ errorMessage
46
+ });
47
+ let defaultHint = "";
48
+ if (defaultValue === true) {
49
+ defaultHint = "[Y/n]";
50
+ } else if (defaultValue === false) {
51
+ defaultHint = "[y/N]";
52
+ } else {
53
+ defaultHint = "[y/n]";
54
+ }
55
+ const fullPrompt = `${question}${colorize(defaultHint, hintColor)}: `;
56
+ const formattedPrompt = fmt({
57
+ type: "M_NULL",
58
+ title: fullPrompt
59
+ });
60
+ const questionLines = countLines(formattedPrompt);
61
+ linesToDelete = questionLines + 1;
62
+ const answer = (await rl.question(formattedPrompt)).toLowerCase().trim();
63
+ let value;
64
+ const formattedBar = bar({ borderColor });
65
+ if (!answer && defaultValue !== void 0) {
66
+ const injectedAnswer = defaultValue === true ? "y" : "n";
67
+ process.stdout.write(`${formattedBar} ${injectedAnswer}
68
+ `);
69
+ value = defaultValue;
70
+ } else if (answer === "y" || answer === "yes") {
71
+ value = true;
72
+ } else if (answer === "n" || answer === "no") {
73
+ value = false;
74
+ } else {
75
+ errorMessage = 'Please answer with "y" or "n".';
76
+ continue;
77
+ }
78
+ let isValid = true;
79
+ errorMessage = "";
80
+ if (schema) {
81
+ isValid = Value.Check(schema, value);
82
+ if (!isValid) {
83
+ const errors = [...Value.Errors(schema, value)];
84
+ errorMessage = errors.length > 0 ? errors[0]?.message ?? "Invalid input." : "Invalid input.";
85
+ }
86
+ }
87
+ if (isValid) {
88
+ msg({ type: "M_NEWLINE" });
89
+ rl.close();
90
+ if (action && value) {
91
+ await action();
92
+ }
93
+ return value;
94
+ }
95
+ }
96
+ } finally {
97
+ rl.close();
98
+ }
99
+ }
@@ -0,0 +1,6 @@
1
+ import { type TSchema } from "@sinclair/typebox";
2
+ import type { PromptOptions } from "../types/prod";
3
+ export declare function datePrompt<T extends TSchema>(options: PromptOptions<T> & {
4
+ dateFormat: string;
5
+ dateKind: "birthday" | "other";
6
+ }): Promise<string>;
@@ -0,0 +1,125 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { Value } from "@sinclair/typebox/value";
3
+ import { stdin as input, stdout as output } from "node:process";
4
+ import readline from "node:readline/promises";
5
+ import { fmt, msg } from "../utils/messages";
6
+ import { countLines, deleteLastLine, deleteLastLines } from "../utils/terminal";
7
+ const dateFormatSchema = Type.Union([
8
+ Type.RegExp(/^(\d{2})\.(\d{2})\.(\d{4})$/, { description: "DD.MM.YYYY" }),
9
+ Type.RegExp(/^(\d{2})\/(\d{2})\/(\d{4})$/, { description: "MM/DD/YYYY" }),
10
+ Type.RegExp(/^(\d{4})\.(\d{2})\.(\d{2})$/, { description: "YYYY.MM.DD" })
11
+ ]);
12
+ export async function datePrompt(options) {
13
+ const {
14
+ title,
15
+ dateFormat,
16
+ dateKind,
17
+ hint,
18
+ validate,
19
+ defaultValue,
20
+ schema,
21
+ titleColor = "cyanBright",
22
+ answerColor = "none",
23
+ titleTypography = "bold",
24
+ titleVariant,
25
+ content,
26
+ contentColor,
27
+ contentTypography,
28
+ contentVariant,
29
+ borderColor = "viceGradient",
30
+ variantOptions
31
+ } = options;
32
+ const rl = readline.createInterface({ input, output });
33
+ let linesToDelete = 0;
34
+ let errorMessage = "";
35
+ try {
36
+ while (true) {
37
+ if (linesToDelete > 0) {
38
+ deleteLastLines(linesToDelete);
39
+ }
40
+ const question = fmt({
41
+ type: errorMessage !== "" ? "M_ERROR" : "M_GENERAL",
42
+ title: `${title} [Format: ${dateFormat}]`,
43
+ titleColor,
44
+ titleTypography,
45
+ titleVariant,
46
+ content,
47
+ contentColor,
48
+ contentTypography,
49
+ contentVariant,
50
+ borderColor,
51
+ hint,
52
+ variantOptions,
53
+ errorMessage,
54
+ addNewLineBefore: false
55
+ });
56
+ const questionLines = countLines(question);
57
+ linesToDelete = questionLines + 1;
58
+ const answer = (await rl.question(question)).trim() || defaultValue;
59
+ if (answer === defaultValue && defaultValue) {
60
+ deleteLastLine();
61
+ msg({
62
+ type: "M_MIDDLE",
63
+ title: ` ${defaultValue}`,
64
+ titleColor: answerColor
65
+ });
66
+ }
67
+ if (!Value.Check(dateFormatSchema, answer)) {
68
+ errorMessage = `Please enter a valid date in ${dateFormat} format.`;
69
+ continue;
70
+ }
71
+ if (dateKind === "birthday") {
72
+ const parts = answer.split(/[./-]/);
73
+ let date;
74
+ if (dateFormat === "DD.MM.YYYY") {
75
+ date = new Date(
76
+ Number(parts[2]),
77
+ Number(parts[1]) - 1,
78
+ Number(parts[0])
79
+ );
80
+ } else if (dateFormat === "MM/DD/YYYY") {
81
+ date = new Date(
82
+ Number(parts[2]),
83
+ Number(parts[0]) - 1,
84
+ Number(parts[1])
85
+ );
86
+ } else if (dateFormat === "YYYY.MM.DD") {
87
+ date = new Date(
88
+ Number(parts[0]),
89
+ Number(parts[1]) - 1,
90
+ Number(parts[2])
91
+ );
92
+ } else {
93
+ date = new Date(answer);
94
+ }
95
+ if (isNaN(date.getTime()) || date.getFullYear() < 1900 || date > /* @__PURE__ */ new Date()) {
96
+ errorMessage = "Please enter a valid birthday date (e.g., 14.09.1999).";
97
+ continue;
98
+ }
99
+ }
100
+ let isValid = true;
101
+ errorMessage = "";
102
+ if (schema) {
103
+ isValid = Value.Check(schema, answer);
104
+ if (!isValid) {
105
+ const errors = [...Value.Errors(schema, answer)];
106
+ errorMessage = errors[0]?.message ?? "Invalid input.";
107
+ }
108
+ }
109
+ if (validate && isValid) {
110
+ const validation = await validate(answer);
111
+ if (validation !== true) {
112
+ isValid = false;
113
+ errorMessage = typeof validation === "string" ? validation : "Invalid input.";
114
+ }
115
+ }
116
+ if (isValid) {
117
+ msg({ type: "M_NEWLINE" });
118
+ rl.close();
119
+ return answer;
120
+ }
121
+ }
122
+ } finally {
123
+ rl.close();
124
+ }
125
+ }
@@ -0,0 +1,2 @@
1
+ import type { PromptOptions } from "../types/prod";
2
+ export declare function endPrompt({ title, titleColor, answerColor, titleTypography, titleVariant, titleAnimation, titleAnimationDelay, border, borderColor, }: PromptOptions): Promise<void>;
@@ -0,0 +1,36 @@
1
+ import { animateText } from "../components/animate";
2
+ import { msg } from "../utils/messages";
3
+ export async function endPrompt({
4
+ title,
5
+ titleColor = "cyanBright",
6
+ answerColor = "none",
7
+ titleTypography = "bold",
8
+ titleVariant,
9
+ titleAnimation,
10
+ titleAnimationDelay,
11
+ border = true,
12
+ borderColor = "viceGradient"
13
+ }) {
14
+ if (titleAnimation) {
15
+ await animateText({
16
+ title,
17
+ anim: titleAnimation,
18
+ delay: titleAnimationDelay,
19
+ type: "M_END_ANIMATED",
20
+ titleColor,
21
+ titleTypography,
22
+ border,
23
+ borderColor
24
+ });
25
+ } else {
26
+ msg({
27
+ type: "M_END",
28
+ title,
29
+ titleColor,
30
+ titleTypography,
31
+ titleVariant,
32
+ border,
33
+ borderColor
34
+ });
35
+ }
36
+ }
@@ -0,0 +1,5 @@
1
+ import type { TSchema, Static } from "@sinclair/typebox";
2
+ import type { PromptOptions, PromptType } from "../types/prod";
3
+ export declare function prompt<T extends TSchema>(options: PromptOptions<T> & {
4
+ type: PromptType;
5
+ }): Promise<Record<(typeof options)["id"], Static<T>>>;