@emeryld/manager 0.6.6 → 0.6.7
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/dist/helper-cli/prompts.js +119 -11
- package/dist/helper-cli.js +5 -3
- package/dist/menu.js +12 -48
- package/package.json +1 -1
|
@@ -53,9 +53,16 @@ async function promptWithReadline(entries, title) {
|
|
|
53
53
|
rl.close();
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
function formatInteractiveLines(state, selectedIndex, title) {
|
|
56
|
+
function formatInteractiveLines(state, selectedIndex, title, searchState) {
|
|
57
57
|
const heading = pageHeading(title, state.page, state.pageCount);
|
|
58
58
|
const lines = [heading];
|
|
59
|
+
if (searchState?.active) {
|
|
60
|
+
const queryLabel = searchState.query || '(type to filter)';
|
|
61
|
+
const statusLabel = searchState.hasResults
|
|
62
|
+
? '(enter to run top match)'
|
|
63
|
+
: '(no matches yet)';
|
|
64
|
+
lines.push(colors.dim(`Search: ${queryLabel} ${statusLabel}`));
|
|
65
|
+
}
|
|
59
66
|
state.options.forEach((option, index) => {
|
|
60
67
|
const isSelected = index === selectedIndex;
|
|
61
68
|
const pointer = isSelected ? `${colors.green('➤')} ` : '';
|
|
@@ -69,7 +76,12 @@ function formatInteractiveLines(state, selectedIndex, title) {
|
|
|
69
76
|
: isSelected
|
|
70
77
|
? colors.green(option.entry.displayName)
|
|
71
78
|
: option.entry.displayName;
|
|
72
|
-
|
|
79
|
+
const runHint = searchState?.active &&
|
|
80
|
+
searchState.hasResults &&
|
|
81
|
+
index === 0
|
|
82
|
+
? colors.dim(' (enter to run)')
|
|
83
|
+
: '';
|
|
84
|
+
lines.push(`${pointer}${numberLabel}. ${option.entry.emoji} ${label} ${colors.dim(option.entry.metaLabel)}${runHint}`);
|
|
73
85
|
return;
|
|
74
86
|
}
|
|
75
87
|
const icon = option.action === 'back'
|
|
@@ -85,8 +97,17 @@ function formatInteractiveLines(state, selectedIndex, title) {
|
|
|
85
97
|
const navLabel = option.enabled ? baseLabel : colors.dim(baseLabel);
|
|
86
98
|
lines.push(`${pointer}${numberLabel}. ${icon} ${navLabel}`);
|
|
87
99
|
});
|
|
100
|
+
if (searchState?.active && !searchState.hasResults) {
|
|
101
|
+
lines.push(colors.yellow('No scripts match your search.'));
|
|
102
|
+
}
|
|
88
103
|
lines.push('');
|
|
89
104
|
lines.push(colors.dim(`Use ↑/↓ (or j/k) to move, 1-${PAGE_SIZE} to run, ${PREVIOUS_KEY} prev page (when shown), ${NEXT_KEY} next page (when shown), ${BACK_KEY} back, Enter to confirm, Esc/Ctrl+C to exit.`));
|
|
105
|
+
if (searchState?.active) {
|
|
106
|
+
lines.push(colors.dim('Search mode: type to filter, Backspace clears query or exits, Enter runs the top match.'));
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
lines.push(colors.dim('Press Enter to search scripts by name.'));
|
|
110
|
+
}
|
|
90
111
|
return lines;
|
|
91
112
|
}
|
|
92
113
|
function renderInteractiveList(lines, previousLineCount) {
|
|
@@ -109,9 +130,15 @@ export async function promptForScript(entries, title) {
|
|
|
109
130
|
}
|
|
110
131
|
process.stdout.write('\x1b[?25l');
|
|
111
132
|
return new Promise((resolve) => {
|
|
133
|
+
const baseEntries = entries;
|
|
134
|
+
let filteredEntries = baseEntries;
|
|
112
135
|
let selectedIndex = 0;
|
|
113
136
|
let renderedLines = 0;
|
|
114
|
-
let state = buildVisibleOptions(
|
|
137
|
+
let state = buildVisibleOptions(filteredEntries, 0);
|
|
138
|
+
let searchActive = false;
|
|
139
|
+
let searchQuery = '';
|
|
140
|
+
let pageBeforeSearch = 0;
|
|
141
|
+
let selectionBeforeSearch = 0;
|
|
115
142
|
const cleanup = () => {
|
|
116
143
|
if (renderedLines > 0) {
|
|
117
144
|
process.stdout.write(`\x1b[${renderedLines}A`);
|
|
@@ -135,10 +162,24 @@ export async function promptForScript(entries, title) {
|
|
|
135
162
|
console.log();
|
|
136
163
|
resolve(undefined);
|
|
137
164
|
};
|
|
138
|
-
const
|
|
139
|
-
state = buildVisibleOptions(
|
|
165
|
+
const rebuildState = (page) => {
|
|
166
|
+
state = buildVisibleOptions(filteredEntries, page);
|
|
140
167
|
selectedIndex = Math.min(selectedIndex, state.options.length - 1);
|
|
141
168
|
selectedIndex = Math.max(0, selectedIndex);
|
|
169
|
+
};
|
|
170
|
+
const render = () => {
|
|
171
|
+
const searchState = searchActive
|
|
172
|
+
? {
|
|
173
|
+
active: true,
|
|
174
|
+
query: searchQuery.trim(),
|
|
175
|
+
hasResults: filteredEntries.length > 0,
|
|
176
|
+
}
|
|
177
|
+
: undefined;
|
|
178
|
+
const lines = formatInteractiveLines(state, selectedIndex, title, searchState);
|
|
179
|
+
renderedLines = renderInteractiveList(lines, renderedLines);
|
|
180
|
+
};
|
|
181
|
+
const setPage = (page) => {
|
|
182
|
+
rebuildState(page);
|
|
142
183
|
render();
|
|
143
184
|
};
|
|
144
185
|
const handleNav = (option) => {
|
|
@@ -167,9 +208,47 @@ export async function promptForScript(entries, title) {
|
|
|
167
208
|
}
|
|
168
209
|
handleNav(option);
|
|
169
210
|
};
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
|
|
211
|
+
const matchesSearchTerm = (entry, term) => {
|
|
212
|
+
const haystack = [
|
|
213
|
+
entry.displayName,
|
|
214
|
+
entry.metaLabel,
|
|
215
|
+
entry.script ?? '',
|
|
216
|
+
entry.description ?? '',
|
|
217
|
+
]
|
|
218
|
+
.join(' ')
|
|
219
|
+
.toLowerCase();
|
|
220
|
+
return haystack.includes(term);
|
|
221
|
+
};
|
|
222
|
+
const applySearch = () => {
|
|
223
|
+
const normalized = searchQuery.trim().toLowerCase();
|
|
224
|
+
if (normalized.length > 0) {
|
|
225
|
+
filteredEntries = baseEntries.filter((entry) => matchesSearchTerm(entry, normalized));
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
filteredEntries = baseEntries;
|
|
229
|
+
}
|
|
230
|
+
rebuildState(0);
|
|
231
|
+
selectedIndex = 0;
|
|
232
|
+
render();
|
|
233
|
+
};
|
|
234
|
+
const enterSearchMode = () => {
|
|
235
|
+
if (searchActive)
|
|
236
|
+
return;
|
|
237
|
+
pageBeforeSearch = state.page;
|
|
238
|
+
selectionBeforeSearch = selectedIndex;
|
|
239
|
+
searchActive = true;
|
|
240
|
+
searchQuery = '';
|
|
241
|
+
applySearch();
|
|
242
|
+
};
|
|
243
|
+
const exitSearchMode = () => {
|
|
244
|
+
if (!searchActive)
|
|
245
|
+
return;
|
|
246
|
+
searchActive = false;
|
|
247
|
+
searchQuery = '';
|
|
248
|
+
filteredEntries = baseEntries;
|
|
249
|
+
state = buildVisibleOptions(filteredEntries, pageBeforeSearch);
|
|
250
|
+
selectedIndex = Math.min(selectionBeforeSearch, state.options.length - 1);
|
|
251
|
+
render();
|
|
173
252
|
};
|
|
174
253
|
const onData = (buffer) => {
|
|
175
254
|
const isArrowUp = buffer.equals(Buffer.from([0x1b, 0x5b, 0x41]));
|
|
@@ -177,10 +256,39 @@ export async function promptForScript(entries, title) {
|
|
|
177
256
|
const isCtrlC = buffer.length === 1 && buffer[0] === 0x03;
|
|
178
257
|
const isEnter = buffer.length === 1 && (buffer[0] === 0x0d || buffer[0] === 0x0a);
|
|
179
258
|
const isEscape = buffer.length === 1 && buffer[0] === 0x1b;
|
|
259
|
+
const isBackspace = buffer.length === 1 && (buffer[0] === 0x7f || buffer[0] === 0x08);
|
|
260
|
+
const isPrintable = buffer.length === 1 && buffer[0] >= 0x20 && buffer[0] <= 0x7e;
|
|
180
261
|
if (isCtrlC || isEscape) {
|
|
181
262
|
cleanup();
|
|
182
263
|
process.exit(1);
|
|
183
264
|
}
|
|
265
|
+
if (searchActive) {
|
|
266
|
+
if (isEnter) {
|
|
267
|
+
if (filteredEntries.length > 0) {
|
|
268
|
+
commitSelection(filteredEntries[0]);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
process.stdout.write('\x07');
|
|
272
|
+
}
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (isBackspace) {
|
|
276
|
+
if (searchQuery.length > 0) {
|
|
277
|
+
searchQuery = searchQuery.slice(0, -1);
|
|
278
|
+
applySearch();
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
exitSearchMode();
|
|
282
|
+
}
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (isPrintable) {
|
|
286
|
+
searchQuery += String.fromCharCode(buffer[0]);
|
|
287
|
+
applySearch();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
184
292
|
if (isArrowUp ||
|
|
185
293
|
(buffer.length === 1 && (buffer[0] === 0x6b || buffer[0] === 0x4b))) {
|
|
186
294
|
selectedIndex =
|
|
@@ -195,7 +303,7 @@ export async function promptForScript(entries, title) {
|
|
|
195
303
|
return;
|
|
196
304
|
}
|
|
197
305
|
if (isEnter) {
|
|
198
|
-
|
|
306
|
+
enterSearchMode();
|
|
199
307
|
return;
|
|
200
308
|
}
|
|
201
309
|
if (buffer.length === 1 && buffer[0] >= 0x30 && buffer[0] <= 0x39) {
|
|
@@ -211,10 +319,10 @@ export async function promptForScript(entries, title) {
|
|
|
211
319
|
((buffer[0] >= 0x41 && buffer[0] <= 0x5a) ||
|
|
212
320
|
(buffer[0] >= 0x61 && buffer[0] <= 0x7a))) {
|
|
213
321
|
const char = String.fromCharCode(buffer[0]).toLowerCase();
|
|
214
|
-
const foundIndex =
|
|
322
|
+
const foundIndex = baseEntries.findIndex((entry) => entry.displayName.toLowerCase().startsWith(char));
|
|
215
323
|
if (foundIndex !== -1) {
|
|
216
324
|
const page = Math.floor(foundIndex / PAGE_SIZE);
|
|
217
|
-
state = buildVisibleOptions(
|
|
325
|
+
state = buildVisibleOptions(filteredEntries, page);
|
|
218
326
|
selectedIndex = foundIndex % PAGE_SIZE;
|
|
219
327
|
render();
|
|
220
328
|
}
|
package/dist/helper-cli.js
CHANGED
|
@@ -14,7 +14,7 @@ export async function runHelperCli({ scripts, title = 'Helper CLI', argv = proce
|
|
|
14
14
|
const [firstArg, ...restArgs] = args;
|
|
15
15
|
if (firstArg === '--list' || firstArg === '-l') {
|
|
16
16
|
printScriptList(normalized, title);
|
|
17
|
-
return;
|
|
17
|
+
return false;
|
|
18
18
|
}
|
|
19
19
|
if (firstArg === '--help' || firstArg === '-h') {
|
|
20
20
|
console.log(colors.magenta('Usage:'));
|
|
@@ -22,19 +22,21 @@ export async function runHelperCli({ scripts, title = 'Helper CLI', argv = proce
|
|
|
22
22
|
console.log('\nFlags:');
|
|
23
23
|
console.log(' --list Show available scripts');
|
|
24
24
|
console.log(' --help Show this information');
|
|
25
|
-
return;
|
|
25
|
+
return false;
|
|
26
26
|
}
|
|
27
27
|
const argLooksLikeScript = firstArg && !firstArg.startsWith('-');
|
|
28
28
|
if (argLooksLikeScript) {
|
|
29
29
|
const requested = findScriptEntry(normalized, firstArg);
|
|
30
30
|
if (requested) {
|
|
31
31
|
await runEntry(requested, restArgs);
|
|
32
|
-
return;
|
|
32
|
+
return true;
|
|
33
33
|
}
|
|
34
34
|
console.log(colors.yellow(`Unknown script "${firstArg}". Falling back to interactive selection…`));
|
|
35
35
|
}
|
|
36
36
|
const selection = await promptForScript(normalized, title);
|
|
37
37
|
if (selection) {
|
|
38
38
|
await runEntry(selection, []);
|
|
39
|
+
return true;
|
|
39
40
|
}
|
|
41
|
+
return false;
|
|
40
42
|
}
|
package/dist/menu.js
CHANGED
|
@@ -14,8 +14,7 @@ function formatKindLabel(kind) {
|
|
|
14
14
|
return 'CLI';
|
|
15
15
|
return `${kind.charAt(0).toUpperCase()}${kind.slice(1)}`;
|
|
16
16
|
}
|
|
17
|
-
function makeManagerStepEntries(targets, packages, state
|
|
18
|
-
const includeBack = options?.includeBack ?? true;
|
|
17
|
+
function makeManagerStepEntries(targets, packages, state) {
|
|
19
18
|
return [
|
|
20
19
|
{
|
|
21
20
|
name: 'update dependencies',
|
|
@@ -103,18 +102,6 @@ function makeManagerStepEntries(targets, packages, state, options) {
|
|
|
103
102
|
state.lastStep = 'full';
|
|
104
103
|
},
|
|
105
104
|
},
|
|
106
|
-
...(includeBack
|
|
107
|
-
? [
|
|
108
|
-
{
|
|
109
|
-
name: 'back',
|
|
110
|
-
emoji: '↩️',
|
|
111
|
-
description: 'Pick packages again',
|
|
112
|
-
handler: async () => {
|
|
113
|
-
state.lastStep = 'back';
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
]
|
|
117
|
-
: []),
|
|
118
105
|
];
|
|
119
106
|
}
|
|
120
107
|
function makeDockerEntry(pkg) {
|
|
@@ -207,19 +194,7 @@ export async function runStepLoop(targets, packages) {
|
|
|
207
194
|
emoji: globalEmoji,
|
|
208
195
|
description: 'update/test/build/publish',
|
|
209
196
|
handler: async () => {
|
|
210
|
-
const managerEntries =
|
|
211
|
-
...makeManagerStepEntries(targets, packages, state, {
|
|
212
|
-
includeBack: false,
|
|
213
|
-
}),
|
|
214
|
-
{
|
|
215
|
-
name: 'back',
|
|
216
|
-
emoji: '↩️',
|
|
217
|
-
description: 'Return to package menu',
|
|
218
|
-
handler: () => {
|
|
219
|
-
state.lastStep = undefined;
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
];
|
|
197
|
+
const managerEntries = makeManagerStepEntries(targets, packages, state);
|
|
223
198
|
await runHelperCli({
|
|
224
199
|
title: `Manager actions for ${pkg.name}`,
|
|
225
200
|
scripts: managerEntries,
|
|
@@ -236,38 +211,23 @@ export async function runStepLoop(targets, packages) {
|
|
|
236
211
|
console.log(colors.yellow(`No package.json scripts found for ${pkg.name}.`));
|
|
237
212
|
return;
|
|
238
213
|
}
|
|
239
|
-
const scriptsMenu = [
|
|
240
|
-
...scriptEntries,
|
|
241
|
-
{
|
|
242
|
-
name: 'back',
|
|
243
|
-
emoji: '↩️',
|
|
244
|
-
description: 'Return to package menu',
|
|
245
|
-
handler: () => { },
|
|
246
|
-
},
|
|
247
|
-
];
|
|
248
214
|
await runHelperCli({
|
|
249
215
|
title: `${pkg.name} scripts`,
|
|
250
|
-
scripts:
|
|
216
|
+
scripts: scriptEntries,
|
|
251
217
|
argv: [],
|
|
252
218
|
});
|
|
253
219
|
},
|
|
254
220
|
},
|
|
255
|
-
{
|
|
256
|
-
name: 'back',
|
|
257
|
-
emoji: '↩️',
|
|
258
|
-
description: 'Pick packages again',
|
|
259
|
-
handler: () => {
|
|
260
|
-
state.lastStep = 'back';
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
221
|
];
|
|
264
|
-
await runHelperCli({
|
|
222
|
+
const ranScript = await runHelperCli({
|
|
265
223
|
title: `Actions for ${pkg.name}`,
|
|
266
224
|
scripts: entries,
|
|
267
225
|
argv: [],
|
|
268
226
|
});
|
|
269
|
-
if (
|
|
227
|
+
if (!ranScript) {
|
|
228
|
+
state.lastStep = 'back';
|
|
270
229
|
return state.lastStep;
|
|
230
|
+
}
|
|
271
231
|
// loop to keep showing menu
|
|
272
232
|
}
|
|
273
233
|
}
|
|
@@ -279,11 +239,15 @@ export async function runStepLoop(targets, packages) {
|
|
|
279
239
|
while (true) {
|
|
280
240
|
state.lastStep = undefined;
|
|
281
241
|
const entries = makeManagerStepEntries(targets, packages, state);
|
|
282
|
-
await runHelperCli({
|
|
242
|
+
const ranScript = await runHelperCli({
|
|
283
243
|
title: `Actions for ${targets.length === 1 ? targets[0].name : 'selected packages'}`,
|
|
284
244
|
scripts: entries,
|
|
285
245
|
argv: [], // <- key change
|
|
286
246
|
});
|
|
247
|
+
if (!ranScript) {
|
|
248
|
+
state.lastStep = 'back';
|
|
249
|
+
return state.lastStep;
|
|
250
|
+
}
|
|
287
251
|
if (state.lastStep === 'back')
|
|
288
252
|
return state.lastStep;
|
|
289
253
|
// keep looping to show menu again
|