@emeryld/manager 0.6.5 → 0.6.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.
@@ -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',
@@ -86,7 +86,7 @@ function formatInteractiveLines(state, selectedIndex, title) {
86
86
  lines.push(`${pointer}${numberLabel}. ${icon} ${navLabel}`);
87
87
  });
88
88
  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.`));
89
+ 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.`));
90
90
  return lines;
91
91
  }
92
92
  function renderInteractiveList(lines, previousLineCount) {
@@ -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,6 +9,11 @@ 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 formatKindLabel(kind) {
13
+ if (kind === 'cli')
14
+ return 'CLI';
15
+ return `${kind.charAt(0).toUpperCase()}${kind.slice(1)}`;
16
+ }
12
17
  function makeManagerStepEntries(targets, packages, state, options) {
13
18
  const includeBack = options?.includeBack ?? true;
14
19
  return [
@@ -154,12 +159,15 @@ export function buildPackageSelectionMenu(packages, onStepComplete) {
154
159
  const entries = ordered.map((pkg) => {
155
160
  const marker = getPackageMarker(pkg);
156
161
  const pkgColor = pkg.color ?? 'cyan';
157
- const descriptionMeta = pkg.relativeDir ?? pkg.dirName;
158
- const markerHint = marker.label ? ` · ${marker.label}` : '';
162
+ const descriptionMeta = colors.dim(pkg.relativeDir ?? pkg.dirName);
163
+ const labelBadge = marker.label ? marker.colorize(marker.label) : '';
164
+ const kindLabel = formatKindLabel(marker.kind);
165
+ const kindBadge = marker.kindColorize(kindLabel);
166
+ const badgeMeta = [labelBadge, kindBadge].filter(Boolean).join(' ');
159
167
  return {
160
168
  name: pkg.name ?? pkg.substitute ?? pkg.dirName,
161
169
  emoji: marker.colorize('●'),
162
- description: `${descriptionMeta}${colors.dim(markerHint)}`,
170
+ description: [descriptionMeta, badgeMeta].filter(Boolean).join(' '),
163
171
  color: pkgColor,
164
172
  handler: async () => {
165
173
  const step = await runStepLoop([pkg], packages);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emeryld/manager",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "description": "Interactive manager for pnpm monorepos (update/test/build/publish).",
5
5
  "license": "MIT",
6
6
  "type": "module",