@emeryld/manager 0.3.1 → 0.3.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.
@@ -1,6 +1,7 @@
1
1
  import { mkdir, readdir, readFile, stat } from 'node:fs/promises';
2
2
  import { spawn } from 'node:child_process';
3
3
  import path from 'node:path';
4
+ import { stdin as input } from 'node:process';
4
5
  import { askLine, promptSingleKey } from '../prompts.js';
5
6
  import { colors, logGlobal } from '../utils/log.js';
6
7
  import { workspaceRoot } from './shared.js';
@@ -59,21 +60,117 @@ async function runCommand(cmd, args, cwd = workspaceRoot) {
59
60
  child.on('error', (err) => reject(err));
60
61
  });
61
62
  }
63
+ function formatVariantLines(variants, selected) {
64
+ const heading = colors.magenta('Available templates');
65
+ const lines = [heading];
66
+ variants.forEach((variant, index) => {
67
+ const isSelected = index === selected;
68
+ const pointer = isSelected ? `${colors.green('➤')} ` : ' ';
69
+ const numberLabel = colors.cyan(String(index + 1).padStart(2, ' '));
70
+ const label = isSelected ? colors.green(variant.label) : variant.label;
71
+ const meta = colors.dim(variant.defaultDir);
72
+ lines.push(`${pointer}${numberLabel}. ${label} ${meta}`);
73
+ });
74
+ lines.push('');
75
+ lines.push(colors.dim('Use ↑/↓ (or j/k) to move, digits (1-9,0 for 10) to pick, Enter to confirm, Esc/Ctrl+C to exit.'));
76
+ return lines;
77
+ }
78
+ function renderInteractiveList(lines, previousLineCount) {
79
+ if (previousLineCount > 0) {
80
+ process.stdout.write(`\x1b[${previousLineCount}A`);
81
+ process.stdout.write('\x1b[0J');
82
+ }
83
+ lines.forEach((line) => console.log(line));
84
+ return lines.length;
85
+ }
62
86
  async function promptForVariant() {
63
- const messageLines = [
64
- 'Pick a package template:',
65
- VARIANTS.map((opt, idx) => ` [${idx + 1}] ${opt.label}`).join('\n'),
66
- `Enter 1-${VARIANTS.length}: `,
67
- ];
68
- const message = `${messageLines.join('\n')}`;
69
- const variant = await promptSingleKey(message, (key) => {
70
- const idx = Number.parseInt(key, 10);
71
- if (Number.isInteger(idx) && idx >= 1 && idx <= VARIANTS.length) {
72
- return VARIANTS[idx - 1];
73
- }
74
- return undefined;
87
+ const supportsRawMode = typeof input.setRawMode === 'function' && input.isTTY;
88
+ if (!supportsRawMode) {
89
+ const fallbackMessage = [
90
+ 'Pick a package template:',
91
+ VARIANTS.map((opt, idx) => ` [${idx + 1}] ${opt.label}`).join('\n'),
92
+ `Enter 1-${VARIANTS.length}: `,
93
+ ].join('\n');
94
+ return promptSingleKey(fallbackMessage, (key) => {
95
+ const idx = Number.parseInt(key, 10);
96
+ if (Number.isInteger(idx) && idx >= 1 && idx <= VARIANTS.length) {
97
+ return VARIANTS[idx - 1];
98
+ }
99
+ return undefined;
100
+ });
101
+ }
102
+ const wasRaw = input.isRaw;
103
+ if (!wasRaw) {
104
+ input.setRawMode(true);
105
+ input.resume();
106
+ }
107
+ process.stdout.write('\x1b[?25l');
108
+ return new Promise((resolve) => {
109
+ let selectedIndex = 0;
110
+ let renderedLines = 0;
111
+ const cleanup = () => {
112
+ if (renderedLines > 0) {
113
+ process.stdout.write(`\x1b[${renderedLines}A`);
114
+ process.stdout.write('\x1b[0J');
115
+ renderedLines = 0;
116
+ }
117
+ process.stdout.write('\x1b[?25h');
118
+ if (!wasRaw) {
119
+ input.setRawMode(false);
120
+ input.pause();
121
+ }
122
+ input.removeListener('data', onData);
123
+ };
124
+ const commitSelection = (variant) => {
125
+ cleanup();
126
+ console.log();
127
+ resolve(variant);
128
+ };
129
+ const render = () => {
130
+ const lines = formatVariantLines(VARIANTS, selectedIndex);
131
+ renderedLines = renderInteractiveList(lines, renderedLines);
132
+ };
133
+ const onData = (buffer) => {
134
+ const isArrowUp = buffer.equals(Buffer.from([0x1b, 0x5b, 0x41]));
135
+ const isArrowDown = buffer.equals(Buffer.from([0x1b, 0x5b, 0x42]));
136
+ const isCtrlC = buffer.length === 1 && buffer[0] === 0x03;
137
+ const isEnter = buffer.length === 1 && (buffer[0] === 0x0d || buffer[0] === 0x0a);
138
+ const isEscape = buffer.length === 1 && buffer[0] === 0x1b;
139
+ if (isCtrlC || isEscape) {
140
+ cleanup();
141
+ process.exit(1);
142
+ }
143
+ if (isArrowUp ||
144
+ (buffer.length === 1 && (buffer[0] === 0x6b || buffer[0] === 0x4b))) {
145
+ selectedIndex = (selectedIndex - 1 + VARIANTS.length) % VARIANTS.length;
146
+ render();
147
+ return;
148
+ }
149
+ if (isArrowDown ||
150
+ (buffer.length === 1 && (buffer[0] === 0x6a || buffer[0] === 0x4a))) {
151
+ selectedIndex = (selectedIndex + 1) % VARIANTS.length;
152
+ render();
153
+ return;
154
+ }
155
+ if (isEnter) {
156
+ commitSelection(VARIANTS[selectedIndex]);
157
+ return;
158
+ }
159
+ if (buffer.length === 1 && buffer[0] >= 0x30 && buffer[0] <= 0x39) {
160
+ const numericValue = buffer[0] === 0x30 ? 10 : buffer[0] - 0x30;
161
+ const idx = numericValue - 1;
162
+ if (idx >= 0 && idx < VARIANTS.length) {
163
+ commitSelection(VARIANTS[idx]);
164
+ }
165
+ else {
166
+ process.stdout.write('\x07');
167
+ }
168
+ return;
169
+ }
170
+ };
171
+ input.on('data', onData);
172
+ render();
75
173
  });
76
- return variant;
77
174
  }
78
175
  async function promptForTargetDir(fallback) {
79
176
  const answer = await askLine(`Path for the new package? (${fallback}): `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emeryld/manager",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Interactive manager for pnpm monorepos (update/test/build/publish).",
5
5
  "license": "MIT",
6
6
  "type": "module",