inquirerjs-checkbox-search 0.1.2
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/LICENSE +21 -0
- package/README.md +289 -0
- package/dist/commonjs/index.d.ts +82 -0
- package/dist/commonjs/index.js +437 -0
- package/dist/commonjs/package.json +3 -0
- package/dist/esm/index.d.ts +82 -0
- package/dist/esm/index.js +430 -0
- package/dist/esm/package.json +3 -0
- package/package.json +130 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { createPrompt, useState, useKeypress, usePagination, useEffect, useRef, useMemo, usePrefix, makeTheme, isUpKey, isDownKey, isEnterKey, Separator, } from '@inquirer/core';
|
|
2
|
+
import colors from 'yoctocolors-cjs';
|
|
3
|
+
import figures from '@inquirer/figures';
|
|
4
|
+
import ansiEscapes from 'ansi-escapes';
|
|
5
|
+
/**
|
|
6
|
+
* Default theme for the checkbox-search prompt
|
|
7
|
+
*/
|
|
8
|
+
const checkboxSearchTheme = {
|
|
9
|
+
icon: {
|
|
10
|
+
checked: colors.green(figures.circleFilled),
|
|
11
|
+
unchecked: figures.circle,
|
|
12
|
+
cursor: figures.pointer,
|
|
13
|
+
},
|
|
14
|
+
style: {
|
|
15
|
+
answer: colors.cyan,
|
|
16
|
+
message: colors.cyan,
|
|
17
|
+
error: (text) => colors.yellow(`> ${text}`),
|
|
18
|
+
help: colors.dim,
|
|
19
|
+
highlight: colors.cyan,
|
|
20
|
+
searchTerm: colors.cyan,
|
|
21
|
+
description: colors.dim,
|
|
22
|
+
disabled: colors.dim,
|
|
23
|
+
},
|
|
24
|
+
helpMode: 'always',
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Type guard to check if an item is selectable (not separator or disabled)
|
|
28
|
+
* @param item - The item to check
|
|
29
|
+
* @returns True if the item can be selected
|
|
30
|
+
*/
|
|
31
|
+
function isSelectable(item) {
|
|
32
|
+
return !Separator.isSeparator(item) && !item.disabled;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Type guard to check if an item is checked/selected
|
|
36
|
+
* @param item - The item to check
|
|
37
|
+
* @returns True if the item is selected
|
|
38
|
+
*/
|
|
39
|
+
function isChecked(item) {
|
|
40
|
+
return isSelectable(item) && Boolean(item.checked);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Toggle the selection state of an item
|
|
44
|
+
* @param item - The item to toggle
|
|
45
|
+
* @returns The item with toggled selection state
|
|
46
|
+
*/
|
|
47
|
+
function toggle(item) {
|
|
48
|
+
return isSelectable(item) ? { ...item, checked: !item.checked } : item;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Normalize choice inputs into consistent format
|
|
52
|
+
* @param choices - Raw choices array (strings, choice objects, separators)
|
|
53
|
+
* @returns Normalized array of items
|
|
54
|
+
*/
|
|
55
|
+
function normalizeChoices(choices) {
|
|
56
|
+
return choices.map((choice) => {
|
|
57
|
+
if (Separator.isSeparator(choice))
|
|
58
|
+
return choice;
|
|
59
|
+
if (typeof choice === 'string') {
|
|
60
|
+
return {
|
|
61
|
+
value: choice,
|
|
62
|
+
name: choice,
|
|
63
|
+
short: choice,
|
|
64
|
+
disabled: false,
|
|
65
|
+
checked: false,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const name = choice.name ?? String(choice.value);
|
|
69
|
+
const normalizedChoice = {
|
|
70
|
+
value: choice.value,
|
|
71
|
+
name,
|
|
72
|
+
short: choice.short ?? name,
|
|
73
|
+
disabled: choice.disabled ?? false,
|
|
74
|
+
checked: choice.checked ?? false,
|
|
75
|
+
};
|
|
76
|
+
if (choice.description) {
|
|
77
|
+
normalizedChoice.description = choice.description;
|
|
78
|
+
}
|
|
79
|
+
return normalizedChoice;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Default filter function for static choices
|
|
84
|
+
* @param items - Array of normalized choices
|
|
85
|
+
* @param term - Search term
|
|
86
|
+
* @returns Filtered array of choices
|
|
87
|
+
*/
|
|
88
|
+
function defaultFilter(items, term) {
|
|
89
|
+
if (!term.trim())
|
|
90
|
+
return items;
|
|
91
|
+
const searchTerm = term.toLowerCase().normalize('NFD');
|
|
92
|
+
return items.filter((item) => {
|
|
93
|
+
const name = item.name.toLowerCase().normalize('NFD');
|
|
94
|
+
const description = (item.description ?? '').toLowerCase().normalize('NFD');
|
|
95
|
+
const value = String(item.value).toLowerCase().normalize('NFD');
|
|
96
|
+
return (name.includes(searchTerm) ||
|
|
97
|
+
description.includes(searchTerm) ||
|
|
98
|
+
value.includes(searchTerm));
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Main checkbox-search prompt implementation
|
|
103
|
+
*
|
|
104
|
+
* A multi-select prompt with text filtering/search capability that combines
|
|
105
|
+
* the functionality of checkbox and search prompts from inquirer.js.
|
|
106
|
+
*
|
|
107
|
+
* Features:
|
|
108
|
+
* - Real-time search/filtering of options
|
|
109
|
+
* - Multi-selection with checkboxes
|
|
110
|
+
* - Keyboard navigation and shortcuts
|
|
111
|
+
* - Support for both static and async data sources
|
|
112
|
+
* - Customizable themes and validation
|
|
113
|
+
*
|
|
114
|
+
* @param config - Configuration options for the prompt
|
|
115
|
+
* @param done - Callback function called when prompt completes
|
|
116
|
+
*/
|
|
117
|
+
export default createPrompt((config, done) => {
|
|
118
|
+
// Stable reference for empty array to prevent unnecessary recalculations
|
|
119
|
+
const emptyArray = useMemo(() => [], []);
|
|
120
|
+
// Configuration with defaults
|
|
121
|
+
const { pageSize = 7, loop = true, required, validate = () => true, default: defaultValues = emptyArray, } = config;
|
|
122
|
+
const theme = makeTheme(checkboxSearchTheme, config.theme);
|
|
123
|
+
// State management hooks
|
|
124
|
+
const [status, setStatus] = useState('idle');
|
|
125
|
+
const prefix = usePrefix({ status, theme });
|
|
126
|
+
// Search state
|
|
127
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
128
|
+
const [searchError, setSearchError] = useState();
|
|
129
|
+
const allItemsRef = useRef([]);
|
|
130
|
+
// Initialize choices directly like the original checkbox prompt
|
|
131
|
+
const [allItems, setAllItems] = useState(() => {
|
|
132
|
+
if (config.choices) {
|
|
133
|
+
const normalized = normalizeChoices(config.choices);
|
|
134
|
+
// Apply default selections
|
|
135
|
+
return normalized.map((item) => {
|
|
136
|
+
if (isSelectable(item) && defaultValues.includes(item.value)) {
|
|
137
|
+
return { ...item, checked: true };
|
|
138
|
+
}
|
|
139
|
+
return item;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return [];
|
|
143
|
+
});
|
|
144
|
+
// Compute filtered items based on search term and filtering logic
|
|
145
|
+
const filteredItems = useMemo(() => {
|
|
146
|
+
// Async source mode - use allItems directly (source handles filtering)
|
|
147
|
+
if (config.source) {
|
|
148
|
+
return allItems;
|
|
149
|
+
}
|
|
150
|
+
// Static mode - filter allItems based on search term
|
|
151
|
+
if (!searchTerm.trim()) {
|
|
152
|
+
return allItems;
|
|
153
|
+
}
|
|
154
|
+
// Filter using provided filter function or default case-insensitive filter
|
|
155
|
+
const filterFn = config.filter || defaultFilter;
|
|
156
|
+
const selectableItems = allItems.filter((item) => !Separator.isSeparator(item));
|
|
157
|
+
const filtered = filterFn(selectableItems, searchTerm);
|
|
158
|
+
// Create a set of filtered values for efficient lookup
|
|
159
|
+
const filteredValues = new Set(filtered.map((item) => item.value));
|
|
160
|
+
// Rebuild preserving current allItems state (including checked status)
|
|
161
|
+
const result = [];
|
|
162
|
+
for (const item of allItems) {
|
|
163
|
+
if (Separator.isSeparator(item)) {
|
|
164
|
+
result.push(item);
|
|
165
|
+
}
|
|
166
|
+
else if (filteredValues.has(item.value)) {
|
|
167
|
+
result.push(item); // This preserves the current checked state from allItems
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}, [allItems, searchTerm, config.source, config.filter]);
|
|
172
|
+
const [active, setActive] = useState(0);
|
|
173
|
+
const [errorMsg, setError] = useState();
|
|
174
|
+
// Handle async source - load data based on search term
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (!config.source) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const controller = new AbortController();
|
|
180
|
+
// Set loading state and clear previous errors
|
|
181
|
+
setStatus('loading');
|
|
182
|
+
setSearchError(undefined);
|
|
183
|
+
const result = config.source(searchTerm || undefined, {
|
|
184
|
+
signal: controller.signal,
|
|
185
|
+
});
|
|
186
|
+
// Handle both Promise and non-Promise returns
|
|
187
|
+
Promise.resolve(result)
|
|
188
|
+
.then((choices) => {
|
|
189
|
+
if (controller.signal.aborted)
|
|
190
|
+
return;
|
|
191
|
+
const normalizedChoices = normalizeChoices(choices);
|
|
192
|
+
setAllItems(normalizedChoices);
|
|
193
|
+
setStatus('idle');
|
|
194
|
+
})
|
|
195
|
+
.catch((error) => {
|
|
196
|
+
if (controller.signal.aborted)
|
|
197
|
+
return;
|
|
198
|
+
console.error('Source function error:', error);
|
|
199
|
+
setSearchError(error instanceof Error ? error.message : 'Failed to load choices');
|
|
200
|
+
setStatus('idle');
|
|
201
|
+
});
|
|
202
|
+
return () => {
|
|
203
|
+
controller.abort();
|
|
204
|
+
};
|
|
205
|
+
}, [config.source, searchTerm]);
|
|
206
|
+
// Update ref whenever allItems changes
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
allItemsRef.current = allItems;
|
|
209
|
+
}, [allItems]);
|
|
210
|
+
// Reset active index when filtered items change, but preserve cursor position during selection toggles
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
// Don't reset cursor position if we're just toggling selection on the same items
|
|
213
|
+
// Only reset when the actual set of filtered items changes (not their selection state)
|
|
214
|
+
const currentFilteredValues = filteredItems
|
|
215
|
+
.filter((item) => !Separator.isSeparator(item))
|
|
216
|
+
.map((item) => item.value);
|
|
217
|
+
const activeItem = filteredItems[active];
|
|
218
|
+
const activeValue = activeItem && !Separator.isSeparator(activeItem)
|
|
219
|
+
? activeItem.value
|
|
220
|
+
: null;
|
|
221
|
+
// If the currently active item is still in the filtered list, preserve its position
|
|
222
|
+
if (activeValue && currentFilteredValues.includes(activeValue)) {
|
|
223
|
+
const newActiveIndex = filteredItems.findIndex((item) => !Separator.isSeparator(item) &&
|
|
224
|
+
item.value === activeValue);
|
|
225
|
+
if (newActiveIndex !== -1) {
|
|
226
|
+
setActive(newActiveIndex);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Otherwise reset to 0 (when filtering actually changes the set of items)
|
|
231
|
+
setActive(0);
|
|
232
|
+
}, [filteredItems, active]);
|
|
233
|
+
// Keyboard event handling
|
|
234
|
+
useKeypress((key, rl) => {
|
|
235
|
+
// Allow search input even during loading, but block other actions
|
|
236
|
+
const isNavigationOrAction = key.name === 'up' ||
|
|
237
|
+
key.name === 'down' ||
|
|
238
|
+
key.name === 'tab' ||
|
|
239
|
+
key.name === 'enter' ||
|
|
240
|
+
key.name === 'escape';
|
|
241
|
+
if (status !== 'idle' && isNavigationOrAction) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// Clear any existing errors
|
|
245
|
+
setError(undefined);
|
|
246
|
+
// Handle Escape key - clear search term quickly
|
|
247
|
+
if (key.name === 'escape') {
|
|
248
|
+
rl.line = ''; // Clear readline input first (avoid re-render)
|
|
249
|
+
setSearchTerm(''); // Then update state
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
// Handle navigation
|
|
253
|
+
if (isUpKey(key) || isDownKey(key)) {
|
|
254
|
+
const direction = isUpKey(key) ? -1 : 1;
|
|
255
|
+
const selectableIndexes = filteredItems
|
|
256
|
+
.map((item, index) => ({ item, index }))
|
|
257
|
+
.filter(({ item }) => isSelectable(item))
|
|
258
|
+
.map(({ index }) => index);
|
|
259
|
+
if (selectableIndexes.length === 0)
|
|
260
|
+
return;
|
|
261
|
+
const currentSelectableIndex = selectableIndexes.findIndex((index) => index >= active);
|
|
262
|
+
let nextSelectableIndex = currentSelectableIndex + direction;
|
|
263
|
+
if (loop) {
|
|
264
|
+
if (nextSelectableIndex < 0)
|
|
265
|
+
nextSelectableIndex = selectableIndexes.length - 1;
|
|
266
|
+
if (nextSelectableIndex >= selectableIndexes.length)
|
|
267
|
+
nextSelectableIndex = 0;
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
nextSelectableIndex = Math.max(0, Math.min(nextSelectableIndex, selectableIndexes.length - 1));
|
|
271
|
+
}
|
|
272
|
+
setActive(selectableIndexes[nextSelectableIndex] || 0);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
// Handle selection toggle with tab key ONLY - prevent tab from affecting search text
|
|
276
|
+
if (key.name === 'tab') {
|
|
277
|
+
const activeItem = filteredItems[active];
|
|
278
|
+
if (activeItem && isSelectable(activeItem)) {
|
|
279
|
+
const activeValue = activeItem.value;
|
|
280
|
+
setAllItems(allItems.map((item) => {
|
|
281
|
+
// Compare by value only for robust matching
|
|
282
|
+
if (!Separator.isSeparator(item) && item.value === activeValue) {
|
|
283
|
+
const toggled = toggle(item);
|
|
284
|
+
return toggled;
|
|
285
|
+
}
|
|
286
|
+
return item;
|
|
287
|
+
}));
|
|
288
|
+
}
|
|
289
|
+
return; // Important: return here to prevent tab from being added to search term
|
|
290
|
+
}
|
|
291
|
+
// Handle submission
|
|
292
|
+
if (isEnterKey(key)) {
|
|
293
|
+
const selectedChoices = allItems.filter(isChecked);
|
|
294
|
+
if (required && selectedChoices.length === 0) {
|
|
295
|
+
setError('At least one choice must be selected');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const result = validate(selectedChoices);
|
|
299
|
+
if (typeof result === 'string') {
|
|
300
|
+
setError(result);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (result === false) {
|
|
304
|
+
setError('Invalid selection');
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (typeof result === 'object' && 'then' in result) {
|
|
308
|
+
result
|
|
309
|
+
.then((isValid) => {
|
|
310
|
+
if (typeof isValid === 'string') {
|
|
311
|
+
setError(isValid);
|
|
312
|
+
}
|
|
313
|
+
else if (isValid === false) {
|
|
314
|
+
setError('Invalid selection');
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
setStatus('done');
|
|
318
|
+
done(selectedChoices.map((choice) => choice.value));
|
|
319
|
+
}
|
|
320
|
+
})
|
|
321
|
+
.catch(() => {
|
|
322
|
+
setError('Validation failed');
|
|
323
|
+
});
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
setStatus('done');
|
|
327
|
+
done(selectedChoices.map((choice) => choice.value));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
// Handle all other input as search term updates EXCEPT tab
|
|
331
|
+
// Only update search term for actual typing, not navigation keys
|
|
332
|
+
if (!isNavigationOrAction) {
|
|
333
|
+
setSearchTerm(rl.line);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
// Create renderItem function that's reactive to current state
|
|
337
|
+
const renderItem = useMemo(() => {
|
|
338
|
+
return ({ item, isActive }) => {
|
|
339
|
+
const line = [];
|
|
340
|
+
if (Separator.isSeparator(item)) {
|
|
341
|
+
return colors.dim(item.separator);
|
|
342
|
+
}
|
|
343
|
+
// Look up checked state directly from allItems to get the current state
|
|
344
|
+
const currentItem = allItems.find((allItem) => !Separator.isSeparator(allItem) &&
|
|
345
|
+
allItem.value === item.value);
|
|
346
|
+
const isChecked = currentItem?.checked || false;
|
|
347
|
+
// Helper function to resolve icon (string or function)
|
|
348
|
+
const resolveIcon = (icon, choiceText) => {
|
|
349
|
+
return typeof icon === 'function' ? icon(choiceText) : icon;
|
|
350
|
+
};
|
|
351
|
+
const choiceName = item.name;
|
|
352
|
+
const checkbox = resolveIcon(isChecked ? theme.icon.checked : theme.icon.unchecked, choiceName);
|
|
353
|
+
const cursor = isActive
|
|
354
|
+
? resolveIcon(theme.icon.cursor, choiceName)
|
|
355
|
+
: ' ';
|
|
356
|
+
line.push(cursor, checkbox);
|
|
357
|
+
let text = item.name;
|
|
358
|
+
if (isActive) {
|
|
359
|
+
text = theme.style.highlight(text);
|
|
360
|
+
}
|
|
361
|
+
else if (item.disabled) {
|
|
362
|
+
text = theme.style.disabled(text);
|
|
363
|
+
}
|
|
364
|
+
line.push(text);
|
|
365
|
+
// Show disabled reason if item is disabled
|
|
366
|
+
if (item.disabled) {
|
|
367
|
+
const disabledReason = typeof item.disabled === 'string'
|
|
368
|
+
? item.disabled
|
|
369
|
+
: 'disabled';
|
|
370
|
+
line.push(theme.style.disabled(`(${disabledReason})`));
|
|
371
|
+
}
|
|
372
|
+
else if (item.description) {
|
|
373
|
+
const description = item.description;
|
|
374
|
+
// If using custom description styling, give full control to user (no parentheses)
|
|
375
|
+
// If using default styling, add parentheses for backward compatibility
|
|
376
|
+
const isUsingCustomDescriptionStyle = config.theme?.style?.description !== undefined;
|
|
377
|
+
if (description) {
|
|
378
|
+
if (isUsingCustomDescriptionStyle) {
|
|
379
|
+
line.push(theme.style.description(description));
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
line.push(`(${theme.style.description(description)})`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return line.join(' ');
|
|
387
|
+
};
|
|
388
|
+
}, [allItems, theme, config.theme]);
|
|
389
|
+
// Setup pagination
|
|
390
|
+
const page = usePagination({
|
|
391
|
+
items: filteredItems,
|
|
392
|
+
active,
|
|
393
|
+
renderItem,
|
|
394
|
+
pageSize,
|
|
395
|
+
loop,
|
|
396
|
+
});
|
|
397
|
+
// Render the prompt
|
|
398
|
+
const message = theme.style.message(config.message, status);
|
|
399
|
+
let helpTip = '';
|
|
400
|
+
if (theme.helpMode === 'always') {
|
|
401
|
+
const tips = ['Tab to select', 'Enter to submit'];
|
|
402
|
+
helpTip = `\n${theme.style.help(`(${tips.join(', ')})`)}`;
|
|
403
|
+
}
|
|
404
|
+
let searchLine = '';
|
|
405
|
+
if (config.source || searchTerm || status === 'loading') {
|
|
406
|
+
const searchPrefix = status === 'loading' ? 'Loading...' : 'Search:';
|
|
407
|
+
const styledTerm = searchTerm ? theme.style.searchTerm(searchTerm) : '';
|
|
408
|
+
searchLine = `\n${searchPrefix} ${styledTerm}`;
|
|
409
|
+
}
|
|
410
|
+
let errorLine = '';
|
|
411
|
+
if (errorMsg) {
|
|
412
|
+
errorLine = `\n${theme.style.error(errorMsg)}`;
|
|
413
|
+
}
|
|
414
|
+
if (searchError) {
|
|
415
|
+
errorLine = `\n${theme.style.error(`Error: ${searchError}`)}`;
|
|
416
|
+
}
|
|
417
|
+
let content = '';
|
|
418
|
+
if (status === 'loading') {
|
|
419
|
+
content = '\nLoading choices...';
|
|
420
|
+
}
|
|
421
|
+
else if (filteredItems.length === 0) {
|
|
422
|
+
content = '\nNo choices available';
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
content = `\n${page}`;
|
|
426
|
+
}
|
|
427
|
+
return `${prefix} ${message}${helpTip}${searchLine}${errorLine}${content}${ansiEscapes.cursorHide}`;
|
|
428
|
+
});
|
|
429
|
+
// Re-export Separator for convenience
|
|
430
|
+
export { Separator } from '@inquirer/core';
|
package/package.json
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "inquirerjs-checkbox-search",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "A multi-select prompt with text filtering for inquirer.js",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"answer",
|
|
7
|
+
"answers",
|
|
8
|
+
"ask",
|
|
9
|
+
"checkbox",
|
|
10
|
+
"cli",
|
|
11
|
+
"command",
|
|
12
|
+
"command-line",
|
|
13
|
+
"filter",
|
|
14
|
+
"inquire",
|
|
15
|
+
"inquirer",
|
|
16
|
+
"interface",
|
|
17
|
+
"javascript",
|
|
18
|
+
"menu",
|
|
19
|
+
"multi-select",
|
|
20
|
+
"node",
|
|
21
|
+
"nodejs",
|
|
22
|
+
"prompt",
|
|
23
|
+
"prompts",
|
|
24
|
+
"question",
|
|
25
|
+
"readline",
|
|
26
|
+
"search",
|
|
27
|
+
"select",
|
|
28
|
+
"terminal",
|
|
29
|
+
"tty",
|
|
30
|
+
"ui"
|
|
31
|
+
],
|
|
32
|
+
"homepage": "https://github.com/texarkanine/inquirerjs-checkbox-search#readme",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/texarkanine/inquirerjs-checkbox-search.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/texarkanine/inquirerjs-checkbox-search/issues"
|
|
39
|
+
},
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"author": "Texarkanine <texarkanine@protonmail.com>",
|
|
42
|
+
"sideEffects": false,
|
|
43
|
+
"type": "module",
|
|
44
|
+
"exports": {
|
|
45
|
+
"./package.json": "./package.json",
|
|
46
|
+
".": {
|
|
47
|
+
"import": {
|
|
48
|
+
"types": "./dist/esm/index.d.ts",
|
|
49
|
+
"default": "./dist/esm/index.js"
|
|
50
|
+
},
|
|
51
|
+
"require": {
|
|
52
|
+
"types": "./dist/commonjs/index.d.ts",
|
|
53
|
+
"default": "./dist/commonjs/index.js"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"main": "./dist/commonjs/index.js",
|
|
58
|
+
"module": "./dist/esm/index.js",
|
|
59
|
+
"types": "./dist/commonjs/index.d.ts",
|
|
60
|
+
"files": [
|
|
61
|
+
"dist",
|
|
62
|
+
"README.md",
|
|
63
|
+
"LICENSE"
|
|
64
|
+
],
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "tshy",
|
|
67
|
+
"clean": "rimraf dist .tshy",
|
|
68
|
+
"dev": "tshy --watch",
|
|
69
|
+
"lint": "eslint . --ext .ts --fix",
|
|
70
|
+
"lint:check": "eslint . --ext .ts",
|
|
71
|
+
"format": "prettier --write .",
|
|
72
|
+
"format:check": "prettier --check .",
|
|
73
|
+
"test": "npm run format && npm run lint && npm run typecheck && npm run test:unit",
|
|
74
|
+
"test:ci": "npm run format:check && npm run lint:check && npm run typecheck && npm run test:unit",
|
|
75
|
+
"test:unit": "vitest run",
|
|
76
|
+
"test:coverage": "vitest run --coverage",
|
|
77
|
+
"test:ui": "vitest --ui",
|
|
78
|
+
"typecheck": "tsc --noEmit",
|
|
79
|
+
"attw": "attw --pack",
|
|
80
|
+
"pkg:inspect": "rimraf package-inspect *.tgz && npm run build && npm pack && mkdir package-inspect && tar -xzf *.tgz -C package-inspect && echo '\\n📦 Package extracted to package-inspect/ for inspection'",
|
|
81
|
+
"prepublishOnly": "npm run clean && npm run build && npm run test:ci && npm run attw"
|
|
82
|
+
},
|
|
83
|
+
"dependencies": {
|
|
84
|
+
"@inquirer/core": "^10.1.13",
|
|
85
|
+
"@inquirer/figures": "^1.0.12",
|
|
86
|
+
"@inquirer/type": "^3.0.7",
|
|
87
|
+
"ansi-escapes": "^4.3.2",
|
|
88
|
+
"yoctocolors-cjs": "^2.1.2"
|
|
89
|
+
},
|
|
90
|
+
"devDependencies": {
|
|
91
|
+
"@arethetypeswrong/cli": "^0.18.1",
|
|
92
|
+
"@inquirer/testing": "^2.1.47",
|
|
93
|
+
"@types/node": "^20.0.0",
|
|
94
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
95
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
96
|
+
"@vitest/coverage-v8": "^2.0.0",
|
|
97
|
+
"@vitest/ui": "^2.0.0",
|
|
98
|
+
"eslint": "^9.0.0",
|
|
99
|
+
"eslint-config-prettier": "^9.0.0",
|
|
100
|
+
"prettier": "^3.0.0",
|
|
101
|
+
"rimraf": "^6.0.0",
|
|
102
|
+
"tshy": "^3.0.2",
|
|
103
|
+
"typescript": "^5.6.0",
|
|
104
|
+
"vitest": "^2.0.0"
|
|
105
|
+
},
|
|
106
|
+
"engines": {
|
|
107
|
+
"node": ">=18"
|
|
108
|
+
},
|
|
109
|
+
"publishConfig": {
|
|
110
|
+
"access": "public"
|
|
111
|
+
},
|
|
112
|
+
"tshy": {
|
|
113
|
+
"exclude": [
|
|
114
|
+
"src/**/*.test.ts",
|
|
115
|
+
"src/**/*.spec.ts"
|
|
116
|
+
],
|
|
117
|
+
"exports": {
|
|
118
|
+
"./package.json": "./package.json",
|
|
119
|
+
".": "./src/index.ts"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"peerDependencies": {
|
|
123
|
+
"@types/node": ">=18"
|
|
124
|
+
},
|
|
125
|
+
"peerDependenciesMeta": {
|
|
126
|
+
"@types/node": {
|
|
127
|
+
"optional": true
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|