@emeryld/manager 0.6.5 → 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.
@@ -45,5 +45,5 @@ export function printPaginatedScriptList(state, title) {
45
45
  const display = option.enabled ? label : colors.dim(label);
46
46
  console.log(` ${numberLabel} ${icon} ${display}`);
47
47
  });
48
- console.log(colors.dim(`Enter 1-${PAGE_SIZE} to run, ${PREVIOUS_KEY} for previous page, ${NEXT_KEY} for next page, ${BACK_KEY} to go back, or type a name.`));
48
+ console.log(colors.dim(`Enter 1-${PAGE_SIZE} to run, ${PREVIOUS_KEY} for previous page (if shown), ${NEXT_KEY} for next page (if shown), ${BACK_KEY} to go back, or type a name.`));
49
49
  }
@@ -15,18 +15,22 @@ export function buildVisibleOptions(entries, page) {
15
15
  }));
16
16
  const hasPrevious = safePage > 0;
17
17
  const hasNext = safePage < pageCount - 1;
18
- options.push({
19
- type: 'nav',
20
- action: 'previous',
21
- hotkey: PREVIOUS_KEY,
22
- enabled: hasPrevious,
23
- });
24
- options.push({
25
- type: 'nav',
26
- action: 'next',
27
- hotkey: NEXT_KEY,
28
- enabled: hasNext,
29
- });
18
+ if (hasPrevious) {
19
+ options.push({
20
+ type: 'nav',
21
+ action: 'previous',
22
+ hotkey: PREVIOUS_KEY,
23
+ enabled: true,
24
+ });
25
+ }
26
+ if (hasNext) {
27
+ options.push({
28
+ type: 'nav',
29
+ action: 'next',
30
+ hotkey: NEXT_KEY,
31
+ enabled: true,
32
+ });
33
+ }
30
34
  options.push({
31
35
  type: 'nav',
32
36
  action: 'back',
@@ -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
- lines.push(`${pointer}${numberLabel}. ${option.entry.emoji} ${label} ${colors.dim(option.entry.metaLabel)}`);
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
- lines.push(colors.dim(`Use ↑/↓ (or j/k) to move, 1-${PAGE_SIZE} to run, ${PREVIOUS_KEY} prev page, ${NEXT_KEY} next page, ${BACK_KEY} back, Enter to confirm, Esc/Ctrl+C to exit.`));
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(entries, 0);
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 setPage = (page) => {
139
- state = buildVisibleOptions(entries, page);
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 render = () => {
171
- const lines = formatInteractiveLines(state, selectedIndex, title);
172
- renderedLines = renderInteractiveList(lines, renderedLines);
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
- activateOption(state.options[selectedIndex]);
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 = entries.findIndex((entry) => entry.displayName.toLowerCase().startsWith(char));
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(entries, page);
325
+ state = buildVisibleOptions(filteredEntries, page);
218
326
  selectedIndex = foundIndex % PAGE_SIZE;
219
327
  render();
220
328
  }
@@ -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
  }
@@ -1,5 +1,48 @@
1
1
  import { colors } from "../utils/log.js";
2
2
  import { run } from "../utils/run.js";
3
+ const FRONTEND_INDICATORS = [
4
+ { dep: 'next', label: 'Next' },
5
+ { dep: 'remix', label: 'Remix' },
6
+ { dep: '@remix-run/node', label: 'Remix' },
7
+ { dep: 'expo', label: 'Expo' },
8
+ { dep: 'react-native', label: 'React Native' },
9
+ { dep: 'react', label: 'React' },
10
+ { dep: 'vite', label: 'Vite' },
11
+ { dep: '@vitejs/plugin-react', label: 'Vite' },
12
+ { dep: 'svelte', label: 'Svelte' },
13
+ { dep: '@sveltejs/vite-plugin-svelte', label: 'SvelteKit' },
14
+ { dep: '@angular/core', label: 'Angular' },
15
+ { dep: 'vue', label: 'Vue' },
16
+ ];
17
+ const BACKEND_INDICATORS = [
18
+ { dep: 'express', label: 'Express' },
19
+ { dep: 'fastify', label: 'Fastify' },
20
+ { dep: 'koa', label: 'Koa' },
21
+ { dep: 'hono', label: 'Hono' },
22
+ { dep: '@nestjs/core', label: 'NestJS' },
23
+ { dep: '@trpc/server', label: 'tRPC' },
24
+ ];
25
+ const CLI_INDICATORS = [
26
+ { dep: '@oclif/core', label: 'OClif' },
27
+ { dep: '@oclif/command', label: 'OClif' },
28
+ { dep: 'oclif', label: 'OClif' },
29
+ { dep: 'commander', label: 'Commander' },
30
+ { dep: 'yargs', label: 'Yargs' },
31
+ { dep: 'cac', label: 'CAC' },
32
+ { dep: 'clipanion', label: 'Clipanion' },
33
+ { dep: 'caporal', label: 'Caporal' },
34
+ { dep: 'ink', label: 'Ink' },
35
+ { dep: 'meow', label: 'Meow' },
36
+ { dep: 'enquirer', label: 'Enquirer' },
37
+ { dep: 'zx', label: 'ZX' },
38
+ ];
39
+ const CLI_KEYWORDS = new Set(['cli', 'command-line', 'commandline', 'tooling']);
40
+ const KIND_COLOR_MAP = {
41
+ frontend: colors.magenta,
42
+ backend: colors.green,
43
+ library: colors.cyan,
44
+ cli: colors.red,
45
+ };
3
46
  export function collectDependencies(pkg) {
4
47
  return new Set([
5
48
  ...Object.keys(pkg.json?.dependencies ?? {}),
@@ -37,6 +80,7 @@ function resolveFormatScript(pkg, scripts, dependencies) {
37
80
  description: commandDescription(args),
38
81
  args,
39
82
  available: true,
83
+ isDefault: true,
40
84
  };
41
85
  }
42
86
  return {
@@ -56,6 +100,7 @@ function resolveTypecheckScript(pkg, scripts, dependencies) {
56
100
  description: commandDescription(args),
57
101
  args,
58
102
  available: true,
103
+ isDefault: true,
59
104
  };
60
105
  }
61
106
  return {
@@ -75,6 +120,7 @@ function resolveLintScript(pkg, scripts, dependencies) {
75
120
  description: commandDescription(args),
76
121
  args,
77
122
  available: true,
123
+ isDefault: true,
78
124
  };
79
125
  }
80
126
  return {
@@ -86,7 +132,7 @@ function resolveLintScript(pkg, scripts, dependencies) {
86
132
  function resolveDevScript(pkg, scripts, dependencies, kind) {
87
133
  const devCandidates = kind === 'frontend'
88
134
  ? ['dev', 'start', 'serve']
89
- : kind === 'backend'
135
+ : kind === 'backend' || kind === 'cli'
90
136
  ? ['dev', 'start', 'watch', 'serve', 'start:dev']
91
137
  : ['dev', 'start'];
92
138
  const direct = resolveRunScript(scripts, devCandidates);
@@ -99,6 +145,7 @@ function resolveDevScript(pkg, scripts, dependencies, kind) {
99
145
  description: commandDescription(args),
100
146
  args,
101
147
  available: true,
148
+ isDefault: true,
102
149
  };
103
150
  }
104
151
  if (dependencies.has('vite')) {
@@ -108,6 +155,7 @@ function resolveDevScript(pkg, scripts, dependencies, kind) {
108
155
  description: commandDescription(args),
109
156
  args,
110
157
  available: true,
158
+ isDefault: true,
111
159
  };
112
160
  }
113
161
  if (dependencies.has('expo')) {
@@ -117,6 +165,7 @@ function resolveDevScript(pkg, scripts, dependencies, kind) {
117
165
  description: commandDescription(args),
118
166
  args,
119
167
  available: true,
168
+ isDefault: true,
120
169
  };
121
170
  }
122
171
  return {
@@ -145,7 +194,9 @@ export function makeBaseScriptEntries(pkg) {
145
194
  return BASE_SCRIPT_KEYS.map((key) => {
146
195
  const resolution = resolveBaseScript(pkg, key, scripts, dependencies, marker.kind);
147
196
  const description = resolution.available
148
- ? resolution.description
197
+ ? resolution.isDefault
198
+ ? `${resolution.description} (default)`
199
+ : resolution.description
149
200
  : `No ${key} command detected`;
150
201
  return {
151
202
  name: key,
@@ -162,17 +213,73 @@ export function makeBaseScriptEntries(pkg) {
162
213
  });
163
214
  }
164
215
  export function getPackageMarker(pkg, dependencies = collectDependencies(pkg)) {
165
- const hasFrontend = ['react', 'next', 'expo', 'react-native', 'vite'].some((dep) => dependencies.has(dep));
166
- const hasBackend = ['express', 'fastify', 'hono', 'koa', '@nestjs/core'].some((dep) => dependencies.has(dep));
216
+ const normalizedDeps = new Set([...dependencies].map((dep) => String(dep).toLowerCase().trim()));
217
+ function findIndicator(indicators) {
218
+ for (const indicator of indicators) {
219
+ if (normalizedDeps.has(indicator.dep.toLowerCase())) {
220
+ return indicator;
221
+ }
222
+ }
223
+ return undefined;
224
+ }
167
225
  const pkgType = pkg.json?.type?.toLowerCase();
168
- if (hasFrontend) {
169
- return { label: 'frontend', colorize: colors.magenta, kind: 'frontend' };
226
+ const keywords = (() => {
227
+ const raw = pkg.json?.keywords;
228
+ if (typeof raw === 'string') {
229
+ return raw
230
+ .split(/\s*,\s*/)
231
+ .map((keyword) => keyword.trim())
232
+ .filter(Boolean);
233
+ }
234
+ if (Array.isArray(raw)) {
235
+ return raw
236
+ .map((keyword) => String(keyword).trim())
237
+ .filter(Boolean);
238
+ }
239
+ return [];
240
+ })();
241
+ const hasCliKeyword = keywords
242
+ .map((keyword) => keyword.toLowerCase())
243
+ .some((keyword) => CLI_KEYWORDS.has(keyword));
244
+ const hasBinField = !!pkg.json?.bin &&
245
+ (typeof pkg.json?.bin === 'string' ||
246
+ typeof pkg.json?.bin === 'object' ||
247
+ Array.isArray(pkg.json?.bin));
248
+ const frontendIndicator = findIndicator(FRONTEND_INDICATORS);
249
+ if (frontendIndicator) {
250
+ return {
251
+ label: frontendIndicator.label ?? frontendIndicator.dep,
252
+ colorize: KIND_COLOR_MAP.frontend,
253
+ kind: 'frontend',
254
+ kindColorize: KIND_COLOR_MAP.frontend,
255
+ };
170
256
  }
171
- if (hasBackend) {
172
- return { label: 'backend', colorize: colors.green, kind: 'backend' };
257
+ const backendIndicator = findIndicator(BACKEND_INDICATORS);
258
+ if (backendIndicator) {
259
+ return {
260
+ label: backendIndicator.label ?? backendIndicator.dep,
261
+ colorize: KIND_COLOR_MAP.backend,
262
+ kind: 'backend',
263
+ kindColorize: KIND_COLOR_MAP.backend,
264
+ };
173
265
  }
174
- if (pkgType === 'module') {
175
- return { label: 'esm', colorize: colors.yellow, kind: 'library' };
266
+ const cliIndicator = findIndicator(CLI_INDICATORS);
267
+ if (cliIndicator || hasCliKeyword || hasBinField) {
268
+ const label = cliIndicator?.label ?? 'CLI';
269
+ return {
270
+ label,
271
+ colorize: KIND_COLOR_MAP.cli,
272
+ kind: 'cli',
273
+ kindColorize: KIND_COLOR_MAP.cli,
274
+ };
176
275
  }
177
- return { label: 'node', colorize: colors.cyan, kind: 'library' };
276
+ const isModule = pkgType === 'module';
277
+ const label = isModule ? 'ESM' : 'Node';
278
+ const labelColorizer = isModule ? colors.yellow : colors.cyan;
279
+ return {
280
+ label,
281
+ colorize: labelColorizer,
282
+ kind: 'library',
283
+ kindColorize: KIND_COLOR_MAP.library,
284
+ };
178
285
  }
package/dist/menu.js CHANGED
@@ -9,8 +9,12 @@ import { ensureWorkingTreeCommitted } from './preflight.js';
9
9
  import { openDockerHelper } from './docker.js';
10
10
  import { run } from './utils/run.js';
11
11
  import { makeBaseScriptEntries, getPackageMarker } from './menu/script-helpers.js';
12
- function makeManagerStepEntries(targets, packages, state, options) {
13
- const includeBack = options?.includeBack ?? true;
12
+ function formatKindLabel(kind) {
13
+ if (kind === 'cli')
14
+ return 'CLI';
15
+ return `${kind.charAt(0).toUpperCase()}${kind.slice(1)}`;
16
+ }
17
+ function makeManagerStepEntries(targets, packages, state) {
14
18
  return [
15
19
  {
16
20
  name: 'update dependencies',
@@ -98,18 +102,6 @@ function makeManagerStepEntries(targets, packages, state, options) {
98
102
  state.lastStep = 'full';
99
103
  },
100
104
  },
101
- ...(includeBack
102
- ? [
103
- {
104
- name: 'back',
105
- emoji: '↩️',
106
- description: 'Pick packages again',
107
- handler: async () => {
108
- state.lastStep = 'back';
109
- },
110
- },
111
- ]
112
- : []),
113
105
  ];
114
106
  }
115
107
  function makeDockerEntry(pkg) {
@@ -154,12 +146,15 @@ export function buildPackageSelectionMenu(packages, onStepComplete) {
154
146
  const entries = ordered.map((pkg) => {
155
147
  const marker = getPackageMarker(pkg);
156
148
  const pkgColor = pkg.color ?? 'cyan';
157
- const descriptionMeta = pkg.relativeDir ?? pkg.dirName;
158
- const markerHint = marker.label ? ` · ${marker.label}` : '';
149
+ const descriptionMeta = colors.dim(pkg.relativeDir ?? pkg.dirName);
150
+ const labelBadge = marker.label ? marker.colorize(marker.label) : '';
151
+ const kindLabel = formatKindLabel(marker.kind);
152
+ const kindBadge = marker.kindColorize(kindLabel);
153
+ const badgeMeta = [labelBadge, kindBadge].filter(Boolean).join(' ');
159
154
  return {
160
155
  name: pkg.name ?? pkg.substitute ?? pkg.dirName,
161
156
  emoji: marker.colorize('●'),
162
- description: `${descriptionMeta}${colors.dim(markerHint)}`,
157
+ description: [descriptionMeta, badgeMeta].filter(Boolean).join(' '),
163
158
  color: pkgColor,
164
159
  handler: async () => {
165
160
  const step = await runStepLoop([pkg], packages);
@@ -199,19 +194,7 @@ export async function runStepLoop(targets, packages) {
199
194
  emoji: globalEmoji,
200
195
  description: 'update/test/build/publish',
201
196
  handler: async () => {
202
- const managerEntries = [
203
- ...makeManagerStepEntries(targets, packages, state, {
204
- includeBack: false,
205
- }),
206
- {
207
- name: 'back',
208
- emoji: '↩️',
209
- description: 'Return to package menu',
210
- handler: () => {
211
- state.lastStep = undefined;
212
- },
213
- },
214
- ];
197
+ const managerEntries = makeManagerStepEntries(targets, packages, state);
215
198
  await runHelperCli({
216
199
  title: `Manager actions for ${pkg.name}`,
217
200
  scripts: managerEntries,
@@ -228,38 +211,23 @@ export async function runStepLoop(targets, packages) {
228
211
  console.log(colors.yellow(`No package.json scripts found for ${pkg.name}.`));
229
212
  return;
230
213
  }
231
- const scriptsMenu = [
232
- ...scriptEntries,
233
- {
234
- name: 'back',
235
- emoji: '↩️',
236
- description: 'Return to package menu',
237
- handler: () => { },
238
- },
239
- ];
240
214
  await runHelperCli({
241
215
  title: `${pkg.name} scripts`,
242
- scripts: scriptsMenu,
216
+ scripts: scriptEntries,
243
217
  argv: [],
244
218
  });
245
219
  },
246
220
  },
247
- {
248
- name: 'back',
249
- emoji: '↩️',
250
- description: 'Pick packages again',
251
- handler: () => {
252
- state.lastStep = 'back';
253
- },
254
- },
255
221
  ];
256
- await runHelperCli({
222
+ const ranScript = await runHelperCli({
257
223
  title: `Actions for ${pkg.name}`,
258
224
  scripts: entries,
259
225
  argv: [],
260
226
  });
261
- if (state.lastStep === 'back')
227
+ if (!ranScript) {
228
+ state.lastStep = 'back';
262
229
  return state.lastStep;
230
+ }
263
231
  // loop to keep showing menu
264
232
  }
265
233
  }
@@ -271,11 +239,15 @@ export async function runStepLoop(targets, packages) {
271
239
  while (true) {
272
240
  state.lastStep = undefined;
273
241
  const entries = makeManagerStepEntries(targets, packages, state);
274
- await runHelperCli({
242
+ const ranScript = await runHelperCli({
275
243
  title: `Actions for ${targets.length === 1 ? targets[0].name : 'selected packages'}`,
276
244
  scripts: entries,
277
245
  argv: [], // <- key change
278
246
  });
247
+ if (!ranScript) {
248
+ state.lastStep = 'back';
249
+ return state.lastStep;
250
+ }
279
251
  if (state.lastStep === 'back')
280
252
  return state.lastStep;
281
253
  // keep looping to show menu again
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emeryld/manager",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "Interactive manager for pnpm monorepos (update/test/build/publish).",
5
5
  "license": "MIT",
6
6
  "type": "module",