mepcli 0.2.1 → 0.2.6
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 +61 -17
- package/dist/ansi.d.ts +2 -0
- package/dist/ansi.js +2 -0
- package/dist/base.d.ts +57 -0
- package/dist/base.js +341 -0
- package/dist/core.d.ts +6 -1
- package/dist/core.js +35 -775
- package/dist/input.d.ts +13 -0
- package/dist/input.js +89 -0
- package/dist/prompts/checkbox.d.ts +12 -0
- package/dist/prompts/checkbox.js +114 -0
- package/dist/prompts/confirm.d.ts +7 -0
- package/dist/prompts/confirm.js +42 -0
- package/dist/prompts/date.d.ts +10 -0
- package/dist/prompts/date.js +136 -0
- package/dist/prompts/file.d.ts +13 -0
- package/dist/prompts/file.js +217 -0
- package/dist/prompts/list.d.ts +9 -0
- package/dist/prompts/list.js +88 -0
- package/dist/prompts/multi-select.d.ts +14 -0
- package/dist/prompts/multi-select.js +123 -0
- package/dist/prompts/number.d.ts +10 -0
- package/dist/prompts/number.js +133 -0
- package/dist/prompts/select.d.ts +14 -0
- package/dist/prompts/select.js +148 -0
- package/dist/prompts/slider.d.ts +7 -0
- package/dist/prompts/slider.js +49 -0
- package/dist/prompts/text.d.ts +13 -0
- package/dist/prompts/text.js +321 -0
- package/dist/prompts/toggle.d.ts +7 -0
- package/dist/prompts/toggle.js +41 -0
- package/dist/theme.d.ts +2 -0
- package/dist/theme.js +11 -0
- package/dist/types.d.ts +24 -0
- package/dist/utils.d.ts +23 -0
- package/dist/utils.js +135 -0
- package/example.ts +57 -5
- package/package.json +4 -4
package/dist/input.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export declare class InputParser extends EventEmitter {
|
|
3
|
+
private buffer;
|
|
4
|
+
private timeout;
|
|
5
|
+
private state;
|
|
6
|
+
constructor();
|
|
7
|
+
/**
|
|
8
|
+
* Feed data into the parser.
|
|
9
|
+
*/
|
|
10
|
+
feed(data: Buffer): void;
|
|
11
|
+
private processChar;
|
|
12
|
+
private emitKey;
|
|
13
|
+
}
|
package/dist/input.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/input.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.InputParser = void 0;
|
|
5
|
+
const events_1 = require("events");
|
|
6
|
+
class InputParser extends events_1.EventEmitter {
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
this.buffer = '';
|
|
10
|
+
this.timeout = null;
|
|
11
|
+
this.state = 'NORMAL';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Feed data into the parser.
|
|
15
|
+
*/
|
|
16
|
+
feed(data) {
|
|
17
|
+
// Convert buffer to string.
|
|
18
|
+
// For partial multi-byte sequences at the chunk boundary,
|
|
19
|
+
// buffer.toString() might produce replacement chars.
|
|
20
|
+
// Ideally we should use StringDecoder, but since we are handling KeyPresses,
|
|
21
|
+
// and usually a keypress is complete, simple toString often works.
|
|
22
|
+
// However, the user mentioned fragmentation issues.
|
|
23
|
+
// But InputParser usually receives data from stdin.
|
|
24
|
+
// The core issue of fragmentation is splitting escape codes like \x1b [ A
|
|
25
|
+
const input = data.toString('utf-8');
|
|
26
|
+
for (let i = 0; i < input.length; i++) {
|
|
27
|
+
const char = input[i];
|
|
28
|
+
this.processChar(char);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
processChar(char) {
|
|
32
|
+
if (this.state === 'NORMAL') {
|
|
33
|
+
if (char === '\x1b') {
|
|
34
|
+
this.state = 'ESC';
|
|
35
|
+
this.buffer = char;
|
|
36
|
+
// Start a short timeout to detect bare ESC
|
|
37
|
+
this.timeout = setTimeout(() => {
|
|
38
|
+
this.emitKey(this.buffer);
|
|
39
|
+
this.buffer = '';
|
|
40
|
+
this.state = 'NORMAL';
|
|
41
|
+
}, 20); // 20ms timeout
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
this.emitKey(char);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (this.state === 'ESC') {
|
|
48
|
+
// Cancel timeout as we received more data
|
|
49
|
+
if (this.timeout)
|
|
50
|
+
clearTimeout(this.timeout);
|
|
51
|
+
this.timeout = null;
|
|
52
|
+
this.buffer += char;
|
|
53
|
+
if (char === '[') {
|
|
54
|
+
this.state = 'CSI';
|
|
55
|
+
}
|
|
56
|
+
else if (char === 'O') {
|
|
57
|
+
// SS3 sequence like \x1b O A (Application Cursor Keys)
|
|
58
|
+
// Treat as similar to CSI for collecting the next char
|
|
59
|
+
this.state = 'CSI';
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// Alt + Key or similar (\x1b + char)
|
|
63
|
+
this.emitKey(this.buffer);
|
|
64
|
+
this.buffer = '';
|
|
65
|
+
this.state = 'NORMAL';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (this.state === 'CSI') {
|
|
69
|
+
this.buffer += char;
|
|
70
|
+
// CSI sequences end with 0x40-0x7E
|
|
71
|
+
if (char >= '@' && char <= '~') {
|
|
72
|
+
this.emitKey(this.buffer);
|
|
73
|
+
this.buffer = '';
|
|
74
|
+
this.state = 'NORMAL';
|
|
75
|
+
}
|
|
76
|
+
// Otherwise, we keep buffering (params like 1;2)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
emitKey(key) {
|
|
80
|
+
// Normalize Enter
|
|
81
|
+
if (key === '\r')
|
|
82
|
+
key = '\n';
|
|
83
|
+
// We emit both the raw sequence and a normalized representation if needed,
|
|
84
|
+
// but existing prompt logic handles raw strings like \x1b[A.
|
|
85
|
+
// So we just emit the reconstructed sequence.
|
|
86
|
+
this.emit('keypress', key, Buffer.from(key));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.InputParser = InputParser;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { CheckboxOptions } from '../types';
|
|
3
|
+
export declare class CheckboxPrompt extends Prompt<any[], CheckboxOptions> {
|
|
4
|
+
private selectedIndex;
|
|
5
|
+
private checkedState;
|
|
6
|
+
private errorMsg;
|
|
7
|
+
private scrollTop;
|
|
8
|
+
private readonly pageSize;
|
|
9
|
+
constructor(options: CheckboxOptions);
|
|
10
|
+
protected render(firstRender: boolean): void;
|
|
11
|
+
protected handleInput(char: string): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CheckboxPrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
// --- Implementation: Checkbox Prompt ---
|
|
8
|
+
class CheckboxPrompt extends base_1.Prompt {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.selectedIndex = 0;
|
|
12
|
+
this.errorMsg = '';
|
|
13
|
+
// Pagination state (added for consistency and performance)
|
|
14
|
+
this.scrollTop = 0;
|
|
15
|
+
this.pageSize = 10;
|
|
16
|
+
this.checkedState = options.choices.map(c => !!c.selected);
|
|
17
|
+
}
|
|
18
|
+
render(firstRender) {
|
|
19
|
+
// Adjust Scroll Top
|
|
20
|
+
if (this.selectedIndex < this.scrollTop) {
|
|
21
|
+
this.scrollTop = this.selectedIndex;
|
|
22
|
+
}
|
|
23
|
+
else if (this.selectedIndex >= this.scrollTop + this.pageSize) {
|
|
24
|
+
this.scrollTop = this.selectedIndex - this.pageSize + 1;
|
|
25
|
+
}
|
|
26
|
+
// Ensure we don't scroll past bounds if list is small
|
|
27
|
+
if (this.options.choices.length <= this.pageSize) {
|
|
28
|
+
this.scrollTop = 0;
|
|
29
|
+
}
|
|
30
|
+
let output = '';
|
|
31
|
+
// Header
|
|
32
|
+
const icon = this.errorMsg ? `${theme_1.theme.error}✖` : `${theme_1.theme.success}?`;
|
|
33
|
+
output += `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${theme_1.theme.muted}(Press <space> to select, <enter> to confirm)${ansi_1.ANSI.RESET}`;
|
|
34
|
+
// List
|
|
35
|
+
const choices = this.options.choices;
|
|
36
|
+
const visibleChoices = choices.slice(this.scrollTop, this.scrollTop + this.pageSize);
|
|
37
|
+
visibleChoices.forEach((choice, index) => {
|
|
38
|
+
const actualIndex = this.scrollTop + index;
|
|
39
|
+
output += '\n'; // New line for each item
|
|
40
|
+
const cursor = actualIndex === this.selectedIndex ? `${theme_1.theme.main}❯${ansi_1.ANSI.RESET}` : ' ';
|
|
41
|
+
const isChecked = this.checkedState[actualIndex];
|
|
42
|
+
const checkbox = isChecked
|
|
43
|
+
? `${theme_1.theme.success}◉${ansi_1.ANSI.RESET}`
|
|
44
|
+
: `${theme_1.theme.muted}◯${ansi_1.ANSI.RESET}`;
|
|
45
|
+
const title = actualIndex === this.selectedIndex
|
|
46
|
+
? `${theme_1.theme.main}${choice.title}${ansi_1.ANSI.RESET}`
|
|
47
|
+
: choice.title;
|
|
48
|
+
output += `${cursor} ${checkbox} ${title}`;
|
|
49
|
+
});
|
|
50
|
+
// Indication of more items
|
|
51
|
+
if (choices.length > this.pageSize) {
|
|
52
|
+
const progress = ` ${this.scrollTop + 1}-${Math.min(this.scrollTop + this.pageSize, choices.length)} of ${choices.length}`;
|
|
53
|
+
// Maybe add this to the header or footer?
|
|
54
|
+
// Let's add it to footer or header. Adding to header is cleaner.
|
|
55
|
+
// But I already wrote header.
|
|
56
|
+
// Let's just append it at the bottom if I want, or ignore for now to keep UI minimal.
|
|
57
|
+
}
|
|
58
|
+
if (this.errorMsg) {
|
|
59
|
+
output += `\n${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`;
|
|
60
|
+
}
|
|
61
|
+
this.renderFrame(output);
|
|
62
|
+
}
|
|
63
|
+
handleInput(char) {
|
|
64
|
+
if (char === '\r' || char === '\n') {
|
|
65
|
+
const selectedCount = this.checkedState.filter(Boolean).length;
|
|
66
|
+
const { min = 0, max } = this.options;
|
|
67
|
+
if (selectedCount < min) {
|
|
68
|
+
this.errorMsg = `You must select at least ${min} options.`;
|
|
69
|
+
this.render(false);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (max && selectedCount > max) {
|
|
73
|
+
this.errorMsg = `You can only select up to ${max} options.`;
|
|
74
|
+
this.render(false);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.cleanup();
|
|
78
|
+
// renderFrame cleans up lines, but doesn't print the final state "persisted" if we want to show the result?
|
|
79
|
+
// Usually we clear the prompt or show a summary.
|
|
80
|
+
// MepCLI seems to submit and let the caller decide or just print newline.
|
|
81
|
+
// Base `submit` prints newline.
|
|
82
|
+
const results = this.options.choices
|
|
83
|
+
.filter((_, i) => this.checkedState[i])
|
|
84
|
+
.map(c => c.value);
|
|
85
|
+
if (this._resolve)
|
|
86
|
+
this._resolve(results);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (char === ' ') {
|
|
90
|
+
const currentChecked = this.checkedState[this.selectedIndex];
|
|
91
|
+
const selectedCount = this.checkedState.filter(Boolean).length;
|
|
92
|
+
const { max } = this.options;
|
|
93
|
+
if (!currentChecked && max && selectedCount >= max) {
|
|
94
|
+
this.errorMsg = `Max ${max} selections allowed.`;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this.checkedState[this.selectedIndex] = !currentChecked;
|
|
98
|
+
this.errorMsg = '';
|
|
99
|
+
}
|
|
100
|
+
this.render(false);
|
|
101
|
+
}
|
|
102
|
+
if (this.isUp(char)) { // Up
|
|
103
|
+
this.selectedIndex = this.selectedIndex > 0 ? this.selectedIndex - 1 : this.options.choices.length - 1;
|
|
104
|
+
this.errorMsg = '';
|
|
105
|
+
this.render(false);
|
|
106
|
+
}
|
|
107
|
+
if (this.isDown(char)) { // Down
|
|
108
|
+
this.selectedIndex = this.selectedIndex < this.options.choices.length - 1 ? this.selectedIndex + 1 : 0;
|
|
109
|
+
this.errorMsg = '';
|
|
110
|
+
this.render(false);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.CheckboxPrompt = CheckboxPrompt;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { ConfirmOptions } from '../types';
|
|
3
|
+
export declare class ConfirmPrompt extends Prompt<boolean, ConfirmOptions> {
|
|
4
|
+
constructor(options: ConfirmOptions);
|
|
5
|
+
protected render(firstRender: boolean): void;
|
|
6
|
+
protected handleInput(char: string): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfirmPrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
// --- Implementation: Confirm Prompt ---
|
|
8
|
+
class ConfirmPrompt extends base_1.Prompt {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.value = options.initial ?? true;
|
|
12
|
+
}
|
|
13
|
+
render(firstRender) {
|
|
14
|
+
// Prepare content
|
|
15
|
+
const hint = this.value ? `${ansi_1.ANSI.BOLD}Yes${ansi_1.ANSI.RESET}/no` : `yes/${ansi_1.ANSI.BOLD}No${ansi_1.ANSI.RESET}`;
|
|
16
|
+
let output = `${theme_1.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${theme_1.theme.muted}(${hint})${ansi_1.ANSI.RESET} `;
|
|
17
|
+
const text = this.value ? 'Yes' : 'No';
|
|
18
|
+
output += `${theme_1.theme.main}${text}${ansi_1.ANSI.RESET}`;
|
|
19
|
+
this.renderFrame(output);
|
|
20
|
+
}
|
|
21
|
+
handleInput(char) {
|
|
22
|
+
const c = char.toLowerCase();
|
|
23
|
+
if (c === '\r' || c === '\n') {
|
|
24
|
+
this.submit(this.value);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (c === 'y') {
|
|
28
|
+
this.value = true;
|
|
29
|
+
this.render(false);
|
|
30
|
+
}
|
|
31
|
+
if (c === 'n') {
|
|
32
|
+
this.value = false;
|
|
33
|
+
this.render(false);
|
|
34
|
+
}
|
|
35
|
+
// Allow left/right to toggle as well for better UX
|
|
36
|
+
if (this.isLeft(char) || this.isRight(char)) {
|
|
37
|
+
this.value = !this.value;
|
|
38
|
+
this.render(false);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.ConfirmPrompt = ConfirmPrompt;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { DateOptions } from '../types';
|
|
3
|
+
export declare class DatePrompt extends Prompt<Date, DateOptions> {
|
|
4
|
+
private selectedField;
|
|
5
|
+
private errorMsg;
|
|
6
|
+
private inputBuffer;
|
|
7
|
+
constructor(options: DateOptions);
|
|
8
|
+
protected render(firstRender: boolean): void;
|
|
9
|
+
protected handleInput(char: string): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DatePrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
// --- Implementation: Date Prompt ---
|
|
8
|
+
class DatePrompt extends base_1.Prompt {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.selectedField = 0; // 0: Year, 1: Month, 2: Day, 3: Hour, 4: Minute
|
|
12
|
+
this.errorMsg = '';
|
|
13
|
+
this.inputBuffer = '';
|
|
14
|
+
this.value = options.initial || new Date();
|
|
15
|
+
}
|
|
16
|
+
render(firstRender) {
|
|
17
|
+
// Date formatting
|
|
18
|
+
const y = this.value.getFullYear();
|
|
19
|
+
const m = (this.value.getMonth() + 1).toString().padStart(2, '0');
|
|
20
|
+
const d = this.value.getDate().toString().padStart(2, '0');
|
|
21
|
+
const h = this.value.getHours().toString().padStart(2, '0');
|
|
22
|
+
const min = this.value.getMinutes().toString().padStart(2, '0');
|
|
23
|
+
const fields = [y, m, d, h, min];
|
|
24
|
+
const display = fields.map((val, i) => {
|
|
25
|
+
if (i === this.selectedField)
|
|
26
|
+
return `${theme_1.theme.main}${ansi_1.ANSI.UNDERLINE}${val}${ansi_1.ANSI.RESET}`;
|
|
27
|
+
return val;
|
|
28
|
+
});
|
|
29
|
+
const icon = this.errorMsg ? `${theme_1.theme.error}✖` : `${theme_1.theme.success}?`;
|
|
30
|
+
const dateStr = `${display[0]}-${display[1]}-${display[2]} ${display[3]}:${display[4]}`;
|
|
31
|
+
let output = `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${dateStr} ${theme_1.theme.muted}(Use arrows or type)${ansi_1.ANSI.RESET}`;
|
|
32
|
+
if (this.errorMsg) {
|
|
33
|
+
output += `\n${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`;
|
|
34
|
+
}
|
|
35
|
+
this.renderFrame(output);
|
|
36
|
+
}
|
|
37
|
+
handleInput(char) {
|
|
38
|
+
if (char === '\r' || char === '\n') {
|
|
39
|
+
this.submit(this.value);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (this.isLeft(char)) { // Left
|
|
43
|
+
this.selectedField = Math.max(0, this.selectedField - 1);
|
|
44
|
+
this.inputBuffer = ''; // Reset buffer on move
|
|
45
|
+
this.errorMsg = '';
|
|
46
|
+
this.render(false);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (this.isRight(char)) { // Right
|
|
50
|
+
this.selectedField = Math.min(4, this.selectedField + 1);
|
|
51
|
+
this.inputBuffer = ''; // Reset buffer on move
|
|
52
|
+
this.errorMsg = '';
|
|
53
|
+
this.render(false);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// Support numeric input
|
|
57
|
+
if (/^\d$/.test(char)) {
|
|
58
|
+
const maxLen = this.selectedField === 0 ? 4 : 2;
|
|
59
|
+
let nextBuffer = this.inputBuffer + char;
|
|
60
|
+
if (nextBuffer.length > maxLen) {
|
|
61
|
+
nextBuffer = char;
|
|
62
|
+
}
|
|
63
|
+
const val = parseInt(nextBuffer, 10);
|
|
64
|
+
let valid = true;
|
|
65
|
+
if (this.selectedField === 1 && (val < 1 || val > 12))
|
|
66
|
+
valid = false; // Month
|
|
67
|
+
if (this.selectedField === 2 && (val < 1 || val > 31))
|
|
68
|
+
valid = false; // Day (rough check)
|
|
69
|
+
if (this.selectedField === 3 && (val > 23))
|
|
70
|
+
valid = false; // Hour
|
|
71
|
+
if (this.selectedField === 4 && (val > 59))
|
|
72
|
+
valid = false; // Minute
|
|
73
|
+
if (!valid) {
|
|
74
|
+
nextBuffer = char;
|
|
75
|
+
}
|
|
76
|
+
this.inputBuffer = nextBuffer;
|
|
77
|
+
const finalVal = parseInt(this.inputBuffer, 10);
|
|
78
|
+
const d = new Date(this.value);
|
|
79
|
+
if (this.selectedField === 0) {
|
|
80
|
+
d.setFullYear(finalVal);
|
|
81
|
+
}
|
|
82
|
+
else if (this.selectedField === 1)
|
|
83
|
+
d.setMonth(Math.max(0, Math.min(11, finalVal - 1)));
|
|
84
|
+
else if (this.selectedField === 2)
|
|
85
|
+
d.setDate(Math.max(1, Math.min(31, finalVal)));
|
|
86
|
+
else if (this.selectedField === 3)
|
|
87
|
+
d.setHours(Math.max(0, Math.min(23, finalVal)));
|
|
88
|
+
else if (this.selectedField === 4)
|
|
89
|
+
d.setMinutes(Math.max(0, Math.min(59, finalVal)));
|
|
90
|
+
this.value = d;
|
|
91
|
+
this.errorMsg = '';
|
|
92
|
+
this.render(false);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Support standard and application cursor keys
|
|
96
|
+
const isUp = this.isUp(char);
|
|
97
|
+
const isDown = this.isDown(char);
|
|
98
|
+
if (isUp || isDown) {
|
|
99
|
+
this.inputBuffer = ''; // Reset buffer on arrow move
|
|
100
|
+
const dir = isUp ? 1 : -1;
|
|
101
|
+
const d = new Date(this.value);
|
|
102
|
+
switch (this.selectedField) {
|
|
103
|
+
case 0:
|
|
104
|
+
d.setFullYear(d.getFullYear() + dir);
|
|
105
|
+
break;
|
|
106
|
+
case 1:
|
|
107
|
+
d.setMonth(d.getMonth() + dir);
|
|
108
|
+
break;
|
|
109
|
+
case 2:
|
|
110
|
+
d.setDate(d.getDate() + dir);
|
|
111
|
+
break;
|
|
112
|
+
case 3:
|
|
113
|
+
d.setHours(d.getHours() + dir);
|
|
114
|
+
break;
|
|
115
|
+
case 4:
|
|
116
|
+
d.setMinutes(d.getMinutes() + dir);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
let valid = true;
|
|
120
|
+
if (this.options.min && d < this.options.min) {
|
|
121
|
+
this.errorMsg = 'Date cannot be before minimum allowed.';
|
|
122
|
+
valid = false;
|
|
123
|
+
}
|
|
124
|
+
if (this.options.max && d > this.options.max) {
|
|
125
|
+
this.errorMsg = 'Date cannot be after maximum allowed.';
|
|
126
|
+
valid = false;
|
|
127
|
+
}
|
|
128
|
+
if (valid) {
|
|
129
|
+
this.value = d;
|
|
130
|
+
this.errorMsg = '';
|
|
131
|
+
}
|
|
132
|
+
this.render(false);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
exports.DatePrompt = DatePrompt;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { FileOptions } from '../types';
|
|
3
|
+
export declare class FilePrompt extends Prompt<string, FileOptions> {
|
|
4
|
+
private input;
|
|
5
|
+
private cursor;
|
|
6
|
+
private suggestions;
|
|
7
|
+
private selectedSuggestion;
|
|
8
|
+
private errorMsg;
|
|
9
|
+
constructor(options: FileOptions);
|
|
10
|
+
private updateSuggestions;
|
|
11
|
+
protected render(firstRender: boolean): void;
|
|
12
|
+
protected handleInput(char: string): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FilePrompt = void 0;
|
|
37
|
+
const ansi_1 = require("../ansi");
|
|
38
|
+
const base_1 = require("../base");
|
|
39
|
+
const theme_1 = require("../theme");
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
// --- Implementation: File Prompt ---
|
|
43
|
+
class FilePrompt extends base_1.Prompt {
|
|
44
|
+
constructor(options) {
|
|
45
|
+
super(options);
|
|
46
|
+
this.input = '';
|
|
47
|
+
this.cursor = 0;
|
|
48
|
+
this.suggestions = [];
|
|
49
|
+
this.selectedSuggestion = -1;
|
|
50
|
+
this.errorMsg = '';
|
|
51
|
+
this.input = options.basePath || '';
|
|
52
|
+
this.cursor = this.input.length;
|
|
53
|
+
}
|
|
54
|
+
updateSuggestions() {
|
|
55
|
+
try {
|
|
56
|
+
const dir = path.dirname(this.input) || '.';
|
|
57
|
+
const partial = path.basename(this.input);
|
|
58
|
+
if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
|
|
59
|
+
const files = fs.readdirSync(dir);
|
|
60
|
+
this.suggestions = files
|
|
61
|
+
.filter(f => f.startsWith(partial))
|
|
62
|
+
.filter(f => {
|
|
63
|
+
const fullPath = path.join(dir, f);
|
|
64
|
+
// Handle errors if file doesn't exist or permission denied
|
|
65
|
+
try {
|
|
66
|
+
const isDir = fs.statSync(fullPath).isDirectory();
|
|
67
|
+
if (this.options.onlyDirectories && !isDir)
|
|
68
|
+
return false;
|
|
69
|
+
if (this.options.extensions && !isDir) {
|
|
70
|
+
return this.options.extensions.some(ext => f.endsWith(ext));
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
.map(f => {
|
|
79
|
+
const fullPath = path.join(dir, f);
|
|
80
|
+
try {
|
|
81
|
+
if (fs.statSync(fullPath).isDirectory())
|
|
82
|
+
return f + '/';
|
|
83
|
+
}
|
|
84
|
+
catch (e) { /* ignore */ }
|
|
85
|
+
return f;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
this.suggestions = [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
this.suggestions = [];
|
|
94
|
+
}
|
|
95
|
+
this.selectedSuggestion = -1;
|
|
96
|
+
}
|
|
97
|
+
render(firstRender) {
|
|
98
|
+
// Construct string
|
|
99
|
+
const icon = this.errorMsg ? `${theme_1.theme.error}✖` : `${theme_1.theme.success}?`;
|
|
100
|
+
let output = `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${this.input}`;
|
|
101
|
+
// Suggestions
|
|
102
|
+
if (this.suggestions.length > 0) {
|
|
103
|
+
output += '\n'; // Separate input from suggestions
|
|
104
|
+
const maxShow = 5;
|
|
105
|
+
const displayed = this.suggestions.slice(0, maxShow);
|
|
106
|
+
displayed.forEach((s, i) => {
|
|
107
|
+
if (i > 0)
|
|
108
|
+
output += '\n';
|
|
109
|
+
if (i === this.selectedSuggestion) {
|
|
110
|
+
output += `${theme_1.theme.main}❯ ${s}${ansi_1.ANSI.RESET}`;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
output += ` ${s}`;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
if (this.suggestions.length > maxShow) {
|
|
117
|
+
output += `\n ...and ${this.suggestions.length - maxShow} more`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
this.renderFrame(output);
|
|
121
|
+
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
122
|
+
// Restore Cursor Logic
|
|
123
|
+
// We need to move up to the input line if we printed suggestions.
|
|
124
|
+
// The input line is always the first line (index 0).
|
|
125
|
+
// So we move up by (totalLines - 1).
|
|
126
|
+
const totalLines = this.lastRenderHeight; // renderFrame sets this
|
|
127
|
+
if (totalLines > 1) {
|
|
128
|
+
this.print(`\x1b[${totalLines - 1}A`);
|
|
129
|
+
}
|
|
130
|
+
// Move right
|
|
131
|
+
const prefix = `${icon} ${theme_1.theme.title}${this.options.message} `;
|
|
132
|
+
const prefixLen = this.stripAnsi(prefix).length;
|
|
133
|
+
// Cursor is usually at the end of input unless we add backspace support etc.
|
|
134
|
+
// The cursor property tracks it, but my handleInput simplified it.
|
|
135
|
+
// Let's rely on this.input.length for now since handleInput appends.
|
|
136
|
+
// Ah, handleInput logic below supports cursor pos theoretically but I only see appending?
|
|
137
|
+
// Actually handleInput doesn't support left/right in the original code, it supports down/up for suggestions.
|
|
138
|
+
// So cursor is always at end.
|
|
139
|
+
const targetCol = prefixLen + this.input.length;
|
|
140
|
+
this.print(ansi_1.ANSI.CURSOR_LEFT);
|
|
141
|
+
if (targetCol > 0)
|
|
142
|
+
this.print(`\x1b[${targetCol}C`);
|
|
143
|
+
}
|
|
144
|
+
handleInput(char) {
|
|
145
|
+
if (char === '\t') { // Tab
|
|
146
|
+
if (this.suggestions.length === 1) {
|
|
147
|
+
const dir = path.dirname(this.input);
|
|
148
|
+
this.input = path.join(dir === '.' ? '' : dir, this.suggestions[0]);
|
|
149
|
+
this.cursor = this.input.length;
|
|
150
|
+
this.suggestions = [];
|
|
151
|
+
this.render(false);
|
|
152
|
+
}
|
|
153
|
+
else if (this.suggestions.length > 1) {
|
|
154
|
+
// Cycle or show? For now cycle if selected
|
|
155
|
+
if (this.selectedSuggestion !== -1) {
|
|
156
|
+
const dir = path.dirname(this.input);
|
|
157
|
+
this.input = path.join(dir === '.' ? '' : dir, this.suggestions[this.selectedSuggestion]);
|
|
158
|
+
this.cursor = this.input.length;
|
|
159
|
+
this.suggestions = [];
|
|
160
|
+
this.render(false);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
// Just show suggestions (already done in render loop usually, but update logic ensures it)
|
|
164
|
+
this.updateSuggestions();
|
|
165
|
+
this.render(false);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
this.updateSuggestions();
|
|
170
|
+
this.render(false);
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (char === '\r' || char === '\n') {
|
|
175
|
+
if (this.selectedSuggestion !== -1) {
|
|
176
|
+
const dir = path.dirname(this.input);
|
|
177
|
+
this.input = path.join(dir === '.' ? '' : dir, this.suggestions[this.selectedSuggestion]);
|
|
178
|
+
this.cursor = this.input.length;
|
|
179
|
+
this.suggestions = [];
|
|
180
|
+
this.selectedSuggestion = -1;
|
|
181
|
+
this.render(false);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
this.submit(this.input);
|
|
185
|
+
}
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (this.isDown(char)) { // Down
|
|
189
|
+
if (this.suggestions.length > 0) {
|
|
190
|
+
this.selectedSuggestion = (this.selectedSuggestion + 1) % Math.min(this.suggestions.length, 5);
|
|
191
|
+
this.render(false);
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (this.isUp(char)) { // Up
|
|
196
|
+
if (this.suggestions.length > 0) {
|
|
197
|
+
this.selectedSuggestion = (this.selectedSuggestion - 1 + Math.min(this.suggestions.length, 5)) % Math.min(this.suggestions.length, 5);
|
|
198
|
+
this.render(false);
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (char === '\u0008' || char === '\x7f') { // Backspace
|
|
203
|
+
if (this.input.length > 0) {
|
|
204
|
+
this.input = this.input.slice(0, -1);
|
|
205
|
+
this.updateSuggestions();
|
|
206
|
+
this.render(false);
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (!/^[\x00-\x1F]/.test(char) && !char.startsWith('\x1b')) {
|
|
211
|
+
this.input += char;
|
|
212
|
+
this.updateSuggestions();
|
|
213
|
+
this.render(false);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
exports.FilePrompt = FilePrompt;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { ListOptions } from '../types';
|
|
3
|
+
export declare class ListPrompt extends Prompt<string[], ListOptions> {
|
|
4
|
+
private currentInput;
|
|
5
|
+
private errorMsg;
|
|
6
|
+
constructor(options: ListOptions);
|
|
7
|
+
protected render(firstRender: boolean): void;
|
|
8
|
+
protected handleInput(char: string): void;
|
|
9
|
+
}
|