@emeryld/manager 1.5.3 → 1.5.4

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.
@@ -4,6 +4,25 @@ import { colors, getEntryColor } from './colors.js';
4
4
  import { pageHeading } from './display.js';
5
5
  import { BACK_KEY, buildVisibleOptions, NEXT_KEY, PAGE_SIZE, PREVIOUS_KEY, } from './pagination.js';
6
6
  import { findScriptEntry } from './scripts.js';
7
+ const ANSI_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
8
+ function visibleLength(text) {
9
+ const clean = text.replace(ANSI_REGEX, '');
10
+ let length = 0;
11
+ for (const ch of clean) {
12
+ const code = ch.codePointAt(0) ?? 0;
13
+ length += code > 0xffff ? 2 : 1;
14
+ }
15
+ return length;
16
+ }
17
+ function highlightHoveredLine(text) {
18
+ const columns = process.stdout.columns ?? 80;
19
+ const len = visibleLength(text);
20
+ // Avoid padding to the exact terminal width; that can trigger hard-wrap drift
21
+ // during repeated redraws in some terminals.
22
+ const targetWidth = Math.max(1, columns - 1);
23
+ const padded = len < targetWidth ? `${text}${' '.repeat(targetWidth - len)}` : text;
24
+ return `\x1b[7m${padded}\x1b[0m`;
25
+ }
7
26
  function formatInteractiveLines(state, selectedIndex, selectedEntries, title, searchState) {
8
27
  const heading = pageHeading(title, state.page, state.pageCount);
9
28
  const lines = [heading];
@@ -14,18 +33,28 @@ function formatInteractiveLines(state, selectedIndex, selectedEntries, title, se
14
33
  }
15
34
  state.options.forEach((option, index) => {
16
35
  const isSelectedRow = index === selectedIndex;
17
- const pointer = isSelectedRow ? `${colors.green('➤')} ` : '';
36
+ const pointer = isSelectedRow ? '➤ ' : '';
18
37
  const entryColorizer = option.type === 'entry' && option.entry.color
19
38
  ? getEntryColor(option.entry)
20
39
  : colors.cyan;
21
- const numberLabelColorizer = isSelectedRow ? colors.green : entryColorizer;
40
+ const numberLabelColorizer = isSelectedRow ? (text) => text : entryColorizer;
22
41
  const numberLabel = numberLabelColorizer(`${option.hotkey}`.padStart(2, ' '));
23
42
  if (option.type === 'entry') {
24
43
  const isChecked = selectedEntries.has(option.entry);
25
- const checkbox = isChecked ? colors.green('[x]') : colors.dim('[ ]');
26
- const labelColorizer = isSelectedRow ? colors.green : entryColorizer;
44
+ const checkbox = isChecked
45
+ ? isSelectedRow
46
+ ? '[x]'
47
+ : colors.green('[x]')
48
+ : isSelectedRow
49
+ ? '[ ]'
50
+ : colors.dim('[ ]');
51
+ const labelColorizer = isSelectedRow ? (text) => text : entryColorizer;
27
52
  const label = labelColorizer(option.entry.displayName);
28
- lines.push(`${pointer}${numberLabel}. ${checkbox} ${option.entry.emoji} ${label} ${colors.dim(option.entry.metaLabel)}`);
53
+ const metaLabel = isSelectedRow
54
+ ? option.entry.metaLabel
55
+ : colors.dim(option.entry.metaLabel);
56
+ const row = `${pointer}${numberLabel}. ${checkbox} ${option.entry.emoji} ${label} ${metaLabel}`;
57
+ lines.push(isSelectedRow ? highlightHoveredLine(row) : row);
29
58
  return;
30
59
  }
31
60
  const icon = option.action === 'back'
@@ -38,12 +67,9 @@ function formatInteractiveLines(state, selectedIndex, selectedEntries, title, se
38
67
  : option.action === 'next'
39
68
  ? 'Next page'
40
69
  : 'Back';
41
- const navLabel = isSelectedRow && option.enabled
42
- ? colors.green(baseLabel)
43
- : option.enabled
44
- ? baseLabel
45
- : colors.dim(baseLabel);
46
- lines.push(`${pointer}${numberLabel}. ${icon} ${navLabel}`);
70
+ const navLabel = option.enabled ? baseLabel : colors.dim(baseLabel);
71
+ const row = `${pointer}${numberLabel}. ${icon} ${navLabel}`;
72
+ lines.push(isSelectedRow ? highlightHoveredLine(row) : row);
47
73
  });
48
74
  if (searchState?.active && !searchState.hasResults) {
49
75
  lines.push(colors.yellow('No scripts match your search.'));
@@ -54,16 +80,6 @@ function formatInteractiveLines(state, selectedIndex, selectedEntries, title, se
54
80
  }
55
81
  function renderInteractiveList(lines, previousLineCount) {
56
82
  const columns = process.stdout.columns ?? 80;
57
- const ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
58
- const visibleLength = (text) => {
59
- const clean = text.replace(ansiRegex, '');
60
- let length = 0;
61
- for (const ch of clean) {
62
- const code = ch.codePointAt(0) ?? 0;
63
- length += code > 0xffff ? 2 : 1;
64
- }
65
- return length;
66
- };
67
83
  const nextLineCount = lines.reduce((total, line) => {
68
84
  const len = visibleLength(line);
69
85
  const wrapped = Math.max(1, Math.ceil(len / columns));
@@ -53,6 +53,25 @@ async function promptWithReadline(entries, title) {
53
53
  rl.close();
54
54
  }
55
55
  }
56
+ const ANSI_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
57
+ function visibleLength(text) {
58
+ const clean = text.replace(ANSI_REGEX, '');
59
+ let length = 0;
60
+ for (const ch of clean) {
61
+ const code = ch.codePointAt(0) ?? 0;
62
+ length += code > 0xffff ? 2 : 1;
63
+ }
64
+ return length;
65
+ }
66
+ function highlightHoveredLine(text) {
67
+ const columns = process.stdout.columns ?? 80;
68
+ const len = visibleLength(text);
69
+ // Avoid padding to the exact terminal width; that can trigger hard-wrap drift
70
+ // during repeated redraws in some terminals.
71
+ const targetWidth = Math.max(1, columns - 1);
72
+ const padded = len < targetWidth ? `${text}${' '.repeat(targetWidth - len)}` : text;
73
+ return `\x1b[7m${padded}\x1b[0m`;
74
+ }
56
75
  function formatInteractiveLines(state, selectedIndex, title, searchState) {
57
76
  const heading = pageHeading(title, state.page, state.pageCount);
58
77
  const lines = [heading];
@@ -62,21 +81,24 @@ function formatInteractiveLines(state, selectedIndex, title, searchState) {
62
81
  }
63
82
  state.options.forEach((option, index) => {
64
83
  const isSelected = index === selectedIndex;
65
- const pointer = isSelected ? `${colors.green('➤')} ` : '';
84
+ const pointer = isSelected ? '➤ ' : '';
66
85
  const entryColorizer = option.type === 'entry' && option.entry.color
67
86
  ? getEntryColor(option.entry)
68
87
  : colors.cyan;
69
- const numberLabelColorizer = isSelected ? colors.green : entryColorizer;
88
+ const numberLabelColorizer = isSelected ? (text) => text : entryColorizer;
70
89
  const numberLabel = numberLabelColorizer(`${option.hotkey}`.padStart(2, ' '));
71
90
  if (option.type === 'entry') {
72
- const labelColorizer = isSelected ? colors.green : entryColorizer;
91
+ const labelColorizer = isSelected ? (text) => text : entryColorizer;
73
92
  const label = labelColorizer(option.entry.displayName);
74
93
  const runHint = searchState?.active &&
75
94
  searchState.hasResults &&
76
95
  index === selectedIndex
77
- ? colors.dim(' (enter to run)')
96
+ ? isSelected
97
+ ? ' (enter to run)'
98
+ : colors.dim(' (enter to run)')
78
99
  : '';
79
- lines.push(`${pointer}${numberLabel}. ${option.entry.emoji} ${label} ${colors.dim(option.entry.metaLabel)}${runHint}`);
100
+ const row = `${pointer}${numberLabel}. ${option.entry.emoji} ${label} ${isSelected ? option.entry.metaLabel : colors.dim(option.entry.metaLabel)}${runHint}`;
101
+ lines.push(isSelected ? highlightHoveredLine(row) : row);
80
102
  return;
81
103
  }
82
104
  const icon = option.action === 'back'
@@ -89,12 +111,9 @@ function formatInteractiveLines(state, selectedIndex, title, searchState) {
89
111
  : option.action === 'next'
90
112
  ? 'Next page'
91
113
  : 'Back';
92
- const navLabel = isSelected && option.enabled
93
- ? colors.green(baseLabel)
94
- : option.enabled
95
- ? baseLabel
96
- : colors.dim(baseLabel);
97
- lines.push(`${pointer}${numberLabel}. ${icon} ${navLabel}`);
114
+ const navLabel = option.enabled ? baseLabel : colors.dim(baseLabel);
115
+ const row = `${pointer}${numberLabel}. ${icon} ${navLabel}`;
116
+ lines.push(isSelected ? highlightHoveredLine(row) : row);
98
117
  });
99
118
  if (searchState?.active && !searchState.hasResults) {
100
119
  lines.push(colors.yellow('No scripts match your search.'));
@@ -111,16 +130,6 @@ function formatInteractiveLines(state, selectedIndex, title, searchState) {
111
130
  }
112
131
  function renderInteractiveList(lines, previousLineCount) {
113
132
  const columns = process.stdout.columns ?? 80;
114
- const ansiRegex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
115
- const visibleLength = (text) => {
116
- const clean = text.replace(ansiRegex, '');
117
- let length = 0;
118
- for (const ch of clean) {
119
- const code = ch.codePointAt(0) ?? 0;
120
- length += code > 0xffff ? 2 : 1;
121
- }
122
- return length;
123
- };
124
133
  const nextLineCount = lines.reduce((total, line) => {
125
134
  const len = visibleLength(line);
126
135
  const wrapped = Math.max(1, Math.ceil(len / columns));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emeryld/manager",
3
- "version": "1.5.3",
3
+ "version": "1.5.4",
4
4
  "description": "Interactive manager for pnpm monorepos (update/test/build/publish).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,7 +29,7 @@
29
29
  "typescript": "^5.9.3"
30
30
  },
31
31
  "devDependencies": {
32
- "@types/node": "^20.19.33",
32
+ "@types/node": "^20.19.37",
33
33
  "@types/semver": "^7.7.1",
34
34
  "cross-env": "^7.0.3"
35
35
  },
@@ -41,6 +41,7 @@
41
41
  },
42
42
  "scripts": {
43
43
  "manager-cli": "node dist/manager-cli.js",
44
+ "update:agent-context": "node tools/update-agent-context.js",
44
45
  "build": "tsc -p tsconfig.base.json && node scripts/copy-manager-cli.mjs",
45
46
  "typecheck": "tsc -p tsconfig.base.json --noEmit",
46
47
  "test": "cross-env TS_NODE_PROJECT=tsconfig.test.json node --loader ts-node/esm --test test/*.test.ts"