@emeryld/manager 0.6.2 โ†’ 0.6.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.
@@ -1,9 +1,8 @@
1
1
  import { mkdir, readdir, readFile, rm, stat } from 'node:fs/promises';
2
- import { spawn } from 'node:child_process';
3
2
  import path from 'node:path';
4
- import { stdin as input } from 'node:process';
5
- import { askLine, promptSingleKey } from '../prompts.js';
3
+ import { askLine } from '../prompts.js';
6
4
  import { colors, logGlobal } from '../utils/log.js';
5
+ import { run } from '../utils/run.js';
7
6
  import { runHelperCli } from '../helper-cli.js';
8
7
  import { loadPackages } from '../packages.js';
9
8
  import { SCRIPT_DESCRIPTIONS, workspaceRoot, ensureWorkspaceToolingFiles, } from './shared.js';
@@ -83,7 +82,7 @@ async function promptForClientKind(existing) {
83
82
  let selection;
84
83
  const scripts = CLIENT_KIND_OPTIONS.map((opt) => ({
85
84
  name: opt.label,
86
- emoji: '๐Ÿ’ป',
85
+ emoji: opt.id == 'vite-react' ? '๐Ÿ’ป' : opt.id == 'expo-react-native' ? '๐Ÿ“ฑ' : '๐ŸŒ',
87
86
  description: opt.summary,
88
87
  handler: () => {
89
88
  selection = opt.id;
@@ -257,133 +256,26 @@ async function ensureTargetDir(targetDir, options) {
257
256
  throw error;
258
257
  }
259
258
  }
260
- async function runCommand(cmd, args, cwd = workspaceRoot) {
261
- await new Promise((resolve, reject) => {
262
- const child = spawn(cmd, args, {
263
- cwd,
264
- stdio: 'inherit',
265
- shell: process.platform === 'win32',
266
- });
267
- child.on('exit', (code) => {
268
- if (code === 0)
269
- resolve();
270
- else
271
- reject(new Error(`${cmd} ${args.join(' ')} exited with ${code}`));
272
- });
273
- child.on('error', (err) => reject(err));
274
- });
275
- }
276
- function formatVariantLines(variants, selected) {
277
- const heading = colors.magenta('Available templates');
278
- const lines = [heading];
279
- variants.forEach((variant, index) => {
280
- const isSelected = index === selected;
281
- const pointer = isSelected ? `${colors.green('โžค')} ` : ' ';
282
- const numberLabel = colors.cyan(String(index + 1).padStart(2, ' '));
283
- const label = isSelected ? colors.green(variant.label) : variant.label;
284
- const meta = colors.dim(variant.defaultDir);
285
- lines.push(`${pointer}${numberLabel}. ${label} ${meta}`);
286
- });
287
- lines.push('');
288
- lines.push(colors.dim('Use โ†‘/โ†“ (or j/k) to move, digits (1-9,0 for 10) to run instantly, Enter to confirm, Esc/Ctrl+C to exit.'));
289
- return lines;
290
- }
291
- function renderInteractiveList(lines, previousLineCount) {
292
- if (previousLineCount > 0) {
293
- process.stdout.write(`\x1b[${previousLineCount}A`);
294
- process.stdout.write('\x1b[0J');
295
- }
296
- lines.forEach((line) => console.log(line));
297
- return lines.length;
298
- }
299
259
  async function promptForVariant() {
300
- const supportsRawMode = typeof input.setRawMode === 'function' && input.isTTY;
301
- if (!supportsRawMode) {
302
- const fallbackMessage = [
303
- 'Pick a package template:',
304
- VARIANTS.map((opt, idx) => ` [${idx + 1}] ${opt.label}`).join('\n'),
305
- `Enter 1-${VARIANTS.length}: `,
306
- ].join('\n');
307
- return promptSingleKey(fallbackMessage, (key) => {
308
- const idx = Number.parseInt(key, 10);
309
- if (Number.isInteger(idx) && idx >= 1 && idx <= VARIANTS.length) {
310
- return VARIANTS[idx - 1];
311
- }
312
- return undefined;
260
+ let selection;
261
+ const scripts = VARIANTS.map((variant) => ({
262
+ name: variant.label,
263
+ emoji: 'โœจ',
264
+ description: variant.summary
265
+ ? `${variant.summary} ยท ${variant.defaultDir}`
266
+ : variant.defaultDir,
267
+ handler: () => {
268
+ selection = variant;
269
+ },
270
+ }));
271
+ while (!selection) {
272
+ await runHelperCli({
273
+ title: 'Pick a package template',
274
+ scripts,
275
+ argv: [],
313
276
  });
314
277
  }
315
- const wasRaw = input.isRaw;
316
- if (!wasRaw) {
317
- input.setRawMode(true);
318
- input.resume();
319
- }
320
- process.stdout.write('\x1b[?25l');
321
- return new Promise((resolve) => {
322
- let selectedIndex = 0;
323
- let renderedLines = 0;
324
- const cleanup = () => {
325
- if (renderedLines > 0) {
326
- process.stdout.write(`\x1b[${renderedLines}A`);
327
- process.stdout.write('\x1b[0J');
328
- renderedLines = 0;
329
- }
330
- process.stdout.write('\x1b[?25h');
331
- if (!wasRaw) {
332
- input.setRawMode(false);
333
- input.pause();
334
- }
335
- input.removeListener('data', onData);
336
- };
337
- const commitSelection = (variant) => {
338
- cleanup();
339
- console.log();
340
- resolve(variant);
341
- };
342
- const render = () => {
343
- const lines = formatVariantLines(VARIANTS, selectedIndex);
344
- renderedLines = renderInteractiveList(lines, renderedLines);
345
- };
346
- const onData = (buffer) => {
347
- const isArrowUp = buffer.equals(Buffer.from([0x1b, 0x5b, 0x41]));
348
- const isArrowDown = buffer.equals(Buffer.from([0x1b, 0x5b, 0x42]));
349
- const isCtrlC = buffer.length === 1 && buffer[0] === 0x03;
350
- const isEnter = buffer.length === 1 && (buffer[0] === 0x0d || buffer[0] === 0x0a);
351
- const isEscape = buffer.length === 1 && buffer[0] === 0x1b;
352
- if (isCtrlC || isEscape) {
353
- cleanup();
354
- process.exit(1);
355
- }
356
- if (isArrowUp ||
357
- (buffer.length === 1 && (buffer[0] === 0x6b || buffer[0] === 0x4b))) {
358
- selectedIndex = (selectedIndex - 1 + VARIANTS.length) % VARIANTS.length;
359
- render();
360
- return;
361
- }
362
- if (isArrowDown ||
363
- (buffer.length === 1 && (buffer[0] === 0x6a || buffer[0] === 0x4a))) {
364
- selectedIndex = (selectedIndex + 1) % VARIANTS.length;
365
- render();
366
- return;
367
- }
368
- if (buffer.length === 1 && (buffer[0] === 0x0d || buffer[0] === 0x0a)) {
369
- commitSelection(VARIANTS[selectedIndex]);
370
- return;
371
- }
372
- if (buffer.length === 1 && buffer[0] >= 0x30 && buffer[0] <= 0x39) {
373
- const numericValue = buffer[0] === 0x30 ? 10 : buffer[0] - 0x30;
374
- const idx = numericValue - 1;
375
- if (idx >= 0 && idx < VARIANTS.length) {
376
- commitSelection(VARIANTS[idx]);
377
- }
378
- else {
379
- process.stdout.write('\x07');
380
- }
381
- return;
382
- }
383
- };
384
- input.on('data', onData);
385
- render();
386
- });
278
+ return selection;
387
279
  }
388
280
  async function promptForTargetDir(fallback) {
389
281
  const answer = await askLine(`Path for the new package? (${fallback}): `);
@@ -401,7 +293,7 @@ async function postCreateTasks(targetDir, options) {
401
293
  }
402
294
  try {
403
295
  logGlobal('Running pnpm installโ€ฆ', colors.cyan);
404
- await runCommand('pnpm', ['install'], workspaceRoot);
296
+ await run('pnpm', ['install'], { cwd: workspaceRoot });
405
297
  }
406
298
  catch (error) {
407
299
  logGlobal(`pnpm install failed: ${error instanceof Error ? error.message : String(error)}`, colors.yellow);
@@ -415,7 +307,9 @@ async function postCreateTasks(targetDir, options) {
415
307
  try {
416
308
  if (options?.pkgName) {
417
309
  logGlobal(`Building workspace deps for ${options.pkgName}โ€ฆ`, colors.cyan);
418
- await runCommand('pnpm', ['-r', '--filter', `${options.pkgName}...`, 'build'], workspaceRoot);
310
+ await run('pnpm', ['-r', '--filter', `${options.pkgName}...`, 'build'], {
311
+ cwd: workspaceRoot,
312
+ });
419
313
  return;
420
314
  }
421
315
  }
@@ -425,7 +319,7 @@ async function postCreateTasks(targetDir, options) {
425
319
  // Fallback: build everything
426
320
  try {
427
321
  logGlobal('Building full workspaceโ€ฆ', colors.cyan);
428
- await runCommand('pnpm', ['-r', 'build'], workspaceRoot);
322
+ await run('pnpm', ['-r', 'build'], { cwd: workspaceRoot });
429
323
  return;
430
324
  }
431
325
  catch (error) {
@@ -438,7 +332,9 @@ async function postCreateTasks(targetDir, options) {
438
332
  const pkg = JSON.parse(pkgRaw);
439
333
  if (pkg.scripts?.build) {
440
334
  logGlobal('Running pnpm run build for the new packageโ€ฆ', colors.cyan);
441
- await runCommand('pnpm', ['-C', targetDir, 'run', 'build'], workspaceRoot);
335
+ await run('pnpm', ['-C', targetDir, 'run', 'build'], {
336
+ cwd: workspaceRoot,
337
+ });
442
338
  }
443
339
  }
444
340
  catch (error) {
@@ -353,6 +353,7 @@ export async function packageTsConfig(targetDir, options) {
353
353
  moduleResolution: options?.ModuleResolutionKind !== undefined
354
354
  ? ModuleResolutionKind[options.ModuleResolutionKind]
355
355
  : undefined,
356
+ delcaration: options?.declarationMap,
356
357
  declarationMap: options?.declarationMap,
357
358
  });
358
359
  const config = stripUndefined({
@@ -22,7 +22,7 @@ import { z } from 'zod'
22
22
 
23
23
  const routes = resource('/api')
24
24
  .sub(
25
- resource('health')
25
+ resource('/health')
26
26
  .get({
27
27
  outputSchema: z.object({
28
28
  status: z.literal('ok'),
@@ -309,8 +309,8 @@ function rootPnpmWorkspace(patterns) {
309
309
  }
310
310
  function rootTsconfigBase(baseName, names) {
311
311
  const paths = {
312
- [names.contract]: [`packages/${baseName}-contract/src`],
313
- [`${names.contract}/*`]: [`packages/${baseName}-contract/src/*`],
312
+ [names.contract]: [`packages/${baseName}-contract/dist`],
313
+ [`${names.contract}/*`]: [`packages/${baseName}-contract/dist/*`],
314
314
  [names.server]: [`packages/${baseName}-server/src`],
315
315
  [`${names.server}/*`]: [`packages/${baseName}-server/src/*`],
316
316
  [names.client]: [`packages/${baseName}-client/src`],
@@ -0,0 +1,7 @@
1
+ import { colors as sharedColors } from '../colors-shared.js';
2
+ export const colors = sharedColors;
3
+ export function getEntryColor(entry) {
4
+ if (entry.color && colors[entry.color])
5
+ return colors[entry.color];
6
+ return colors.cyan;
7
+ }
@@ -0,0 +1,49 @@
1
+ import { colors, getEntryColor } from './colors.js';
2
+ import { BACK_KEY, NEXT_KEY, PAGE_SIZE, PREVIOUS_KEY } from './pagination.js';
3
+ export function pageHeading(title, page, pageCount) {
4
+ const heading = title
5
+ ? colors.magenta(title)
6
+ : colors.magenta('Available scripts');
7
+ if (page === undefined || pageCount === undefined || pageCount <= 1) {
8
+ return heading;
9
+ }
10
+ return `${heading} ${colors.dim(`(page ${page + 1}/${pageCount})`)}`;
11
+ }
12
+ export function printScriptList(entries, title) {
13
+ const heading = pageHeading(title);
14
+ console.log(heading);
15
+ entries.forEach((entry, index) => {
16
+ const colorizer = entry.color ? getEntryColor(entry) : colors.cyan;
17
+ const label = entry.color ? colorizer(entry.displayName) : entry.displayName;
18
+ console.log(` ${colorizer(String(index + 1).padStart(2, ' '))} ${entry.emoji} ${label} ${colors.dim(entry.metaLabel)}`);
19
+ });
20
+ }
21
+ export function printPaginatedScriptList(state, title) {
22
+ console.log(pageHeading(title, state.page, state.pageCount));
23
+ state.options.forEach((option) => {
24
+ const colorizer = option.type === 'entry' && option.entry.color
25
+ ? getEntryColor(option.entry)
26
+ : colors.cyan;
27
+ const numberLabel = colorizer(String(option.hotkey).padStart(2, ' '));
28
+ if (option.type === 'entry') {
29
+ const label = option.entry.color
30
+ ? colorizer(option.entry.displayName)
31
+ : option.entry.displayName;
32
+ console.log(` ${numberLabel} ${option.entry.emoji} ${label} ${colors.dim(option.entry.metaLabel)}`);
33
+ return;
34
+ }
35
+ const icon = option.action === 'back'
36
+ ? 'โ†ฉ๏ธ'
37
+ : option.action === 'previous'
38
+ ? 'โฌ…๏ธ'
39
+ : 'โžก๏ธ';
40
+ const label = option.action === 'previous'
41
+ ? 'Previous page'
42
+ : option.action === 'next'
43
+ ? 'Next page'
44
+ : 'Back';
45
+ const display = option.enabled ? label : colors.dim(label);
46
+ console.log(` ${numberLabel} ${icon} ${display}`);
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.`));
49
+ }
@@ -0,0 +1,14 @@
1
+ import { createRequire } from 'node:module';
2
+ import { fileURLToPath } from 'node:url';
3
+ import path from 'node:path';
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ export const managerRoot = path.resolve(path.dirname(__filename), '..', '..');
6
+ export const rootDir = process.cwd();
7
+ export const managerRequire = createRequire(import.meta.url);
8
+ let tsNodeLoaderPath;
9
+ export function getTsNodeLoaderPath() {
10
+ if (!tsNodeLoaderPath) {
11
+ tsNodeLoaderPath = managerRequire.resolve('ts-node/esm.mjs');
12
+ }
13
+ return tsNodeLoaderPath;
14
+ }
@@ -0,0 +1,41 @@
1
+ export const PAGE_SIZE = 7;
2
+ export const PREVIOUS_KEY = 8;
3
+ export const NEXT_KEY = 9;
4
+ export const BACK_KEY = 0;
5
+ export function buildVisibleOptions(entries, page) {
6
+ const pageCount = Math.max(1, Math.ceil(entries.length / PAGE_SIZE));
7
+ const safePage = Math.min(Math.max(page, 0), pageCount - 1);
8
+ const start = safePage * PAGE_SIZE;
9
+ const pageEntries = entries.slice(start, start + PAGE_SIZE);
10
+ const options = pageEntries.map((entry, index) => ({
11
+ type: 'entry',
12
+ entry,
13
+ hotkey: index + 1,
14
+ absoluteIndex: start + index,
15
+ }));
16
+ const hasPrevious = safePage > 0;
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
+ });
30
+ options.push({
31
+ type: 'nav',
32
+ action: 'back',
33
+ hotkey: BACK_KEY,
34
+ enabled: true,
35
+ });
36
+ return {
37
+ options,
38
+ page: safePage,
39
+ pageCount,
40
+ };
41
+ }
@@ -0,0 +1,229 @@
1
+ import readline from 'node:readline/promises';
2
+ import { stdin as input, stdout as output } from 'node:process';
3
+ import { colors, getEntryColor } from './colors.js';
4
+ import { pageHeading, printPaginatedScriptList } from './display.js';
5
+ import { BACK_KEY, buildVisibleOptions, NEXT_KEY, PAGE_SIZE, PREVIOUS_KEY, } from './pagination.js';
6
+ import { findScriptEntry } from './scripts.js';
7
+ async function promptWithReadline(entries, title) {
8
+ let page = 0;
9
+ const rl = readline.createInterface({ input, output });
10
+ try {
11
+ while (true) {
12
+ const state = buildVisibleOptions(entries, page);
13
+ page = state.page;
14
+ printPaginatedScriptList(state, title);
15
+ const answer = (await rl.question(colors.cyan('\nSelect an option by number or name: '))).trim();
16
+ if (!answer)
17
+ continue;
18
+ const numeric = Number.parseInt(answer, 10);
19
+ if (!Number.isNaN(numeric)) {
20
+ const option = state.options.find((opt) => opt.hotkey === numeric);
21
+ if (!option) {
22
+ console.log(colors.yellow(`Unknown option "${answer}". Try again.`));
23
+ continue;
24
+ }
25
+ if (option.type === 'entry') {
26
+ return option.entry;
27
+ }
28
+ if (option.action === 'back') {
29
+ return undefined;
30
+ }
31
+ if (option.action === 'previous') {
32
+ if (option.enabled)
33
+ page = Math.max(0, state.page - 1);
34
+ else
35
+ console.log(colors.yellow('No previous page.'));
36
+ continue;
37
+ }
38
+ if (option.action === 'next') {
39
+ if (option.enabled)
40
+ page = Math.min(state.pageCount - 1, state.page + 1);
41
+ else
42
+ console.log(colors.yellow('No next page.'));
43
+ continue;
44
+ }
45
+ }
46
+ const byName = findScriptEntry(entries, answer);
47
+ if (byName)
48
+ return byName;
49
+ console.log(colors.yellow(`Could not find "${answer}". Try again.`));
50
+ }
51
+ }
52
+ finally {
53
+ rl.close();
54
+ }
55
+ }
56
+ function formatInteractiveLines(state, selectedIndex, title) {
57
+ const heading = pageHeading(title, state.page, state.pageCount);
58
+ const lines = [heading];
59
+ state.options.forEach((option, index) => {
60
+ const isSelected = index === selectedIndex;
61
+ const pointer = isSelected ? `${colors.green('โžค')} ` : '';
62
+ const colorizer = option.type === 'entry' && option.entry.color
63
+ ? getEntryColor(option.entry)
64
+ : colors.cyan;
65
+ const numberLabel = colorizer(`${option.hotkey}`.padStart(2, ' '));
66
+ if (option.type === 'entry') {
67
+ const label = option.entry.color
68
+ ? colorizer(option.entry.displayName)
69
+ : isSelected
70
+ ? colors.green(option.entry.displayName)
71
+ : option.entry.displayName;
72
+ lines.push(`${pointer}${numberLabel}. ${option.entry.emoji} ${label} ${colors.dim(option.entry.metaLabel)}`);
73
+ return;
74
+ }
75
+ const icon = option.action === 'back'
76
+ ? 'โ†ฉ๏ธ'
77
+ : option.action === 'previous'
78
+ ? 'โฌ…๏ธ'
79
+ : 'โžก๏ธ';
80
+ const baseLabel = option.action === 'previous'
81
+ ? 'Previous page'
82
+ : option.action === 'next'
83
+ ? 'Next page'
84
+ : 'Back';
85
+ const navLabel = option.enabled ? baseLabel : colors.dim(baseLabel);
86
+ lines.push(`${pointer}${numberLabel}. ${icon} ${navLabel}`);
87
+ });
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.`));
90
+ return lines;
91
+ }
92
+ function renderInteractiveList(lines, previousLineCount) {
93
+ if (previousLineCount > 0) {
94
+ process.stdout.write(`\x1b[${previousLineCount}A`);
95
+ process.stdout.write('\x1b[0J');
96
+ }
97
+ lines.forEach((line) => console.log(line));
98
+ return lines.length;
99
+ }
100
+ export async function promptForScript(entries, title) {
101
+ const supportsRawMode = typeof input.setRawMode === 'function' && input.isTTY;
102
+ if (!supportsRawMode) {
103
+ return promptWithReadline(entries, title);
104
+ }
105
+ const wasRaw = input.isRaw;
106
+ if (!wasRaw) {
107
+ input.setRawMode(true);
108
+ input.resume();
109
+ }
110
+ process.stdout.write('\x1b[?25l');
111
+ return new Promise((resolve) => {
112
+ let selectedIndex = 0;
113
+ let renderedLines = 0;
114
+ let state = buildVisibleOptions(entries, 0);
115
+ const cleanup = () => {
116
+ if (renderedLines > 0) {
117
+ process.stdout.write(`\x1b[${renderedLines}A`);
118
+ process.stdout.write('\x1b[0J');
119
+ renderedLines = 0;
120
+ }
121
+ process.stdout.write('\x1b[?25h');
122
+ if (!wasRaw) {
123
+ input.setRawMode(false);
124
+ input.pause();
125
+ }
126
+ input.removeListener('data', onData);
127
+ };
128
+ const commitSelection = (entry) => {
129
+ cleanup();
130
+ console.log();
131
+ resolve(entry);
132
+ };
133
+ const commitBack = () => {
134
+ cleanup();
135
+ console.log();
136
+ resolve(undefined);
137
+ };
138
+ const setPage = (page) => {
139
+ state = buildVisibleOptions(entries, page);
140
+ selectedIndex = Math.min(selectedIndex, state.options.length - 1);
141
+ selectedIndex = Math.max(0, selectedIndex);
142
+ render();
143
+ };
144
+ const handleNav = (option) => {
145
+ if (option.action === 'back') {
146
+ commitBack();
147
+ return;
148
+ }
149
+ if (option.action === 'previous') {
150
+ if (option.enabled)
151
+ setPage(state.page - 1);
152
+ else
153
+ process.stdout.write('\x07');
154
+ return;
155
+ }
156
+ if (option.action === 'next') {
157
+ if (option.enabled)
158
+ setPage(state.page + 1);
159
+ else
160
+ process.stdout.write('\x07');
161
+ }
162
+ };
163
+ const activateOption = (option) => {
164
+ if (option.type === 'entry') {
165
+ commitSelection(option.entry);
166
+ return;
167
+ }
168
+ handleNav(option);
169
+ };
170
+ const render = () => {
171
+ const lines = formatInteractiveLines(state, selectedIndex, title);
172
+ renderedLines = renderInteractiveList(lines, renderedLines);
173
+ };
174
+ const onData = (buffer) => {
175
+ const isArrowUp = buffer.equals(Buffer.from([0x1b, 0x5b, 0x41]));
176
+ const isArrowDown = buffer.equals(Buffer.from([0x1b, 0x5b, 0x42]));
177
+ const isCtrlC = buffer.length === 1 && buffer[0] === 0x03;
178
+ const isEnter = buffer.length === 1 && (buffer[0] === 0x0d || buffer[0] === 0x0a);
179
+ const isEscape = buffer.length === 1 && buffer[0] === 0x1b;
180
+ if (isCtrlC || isEscape) {
181
+ cleanup();
182
+ process.exit(1);
183
+ }
184
+ if (isArrowUp ||
185
+ (buffer.length === 1 && (buffer[0] === 0x6b || buffer[0] === 0x4b))) {
186
+ selectedIndex =
187
+ (selectedIndex - 1 + state.options.length) % state.options.length;
188
+ render();
189
+ return;
190
+ }
191
+ if (isArrowDown ||
192
+ (buffer.length === 1 && (buffer[0] === 0x6a || buffer[0] === 0x4a))) {
193
+ selectedIndex = (selectedIndex + 1) % state.options.length;
194
+ render();
195
+ return;
196
+ }
197
+ if (isEnter) {
198
+ activateOption(state.options[selectedIndex]);
199
+ return;
200
+ }
201
+ if (buffer.length === 1 && buffer[0] >= 0x30 && buffer[0] <= 0x39) {
202
+ const numericValue = buffer[0] - 0x30;
203
+ const option = state.options.find((opt) => opt.hotkey === numericValue);
204
+ if (option)
205
+ activateOption(option);
206
+ else
207
+ process.stdout.write('\x07');
208
+ return;
209
+ }
210
+ if (buffer.length === 1 &&
211
+ ((buffer[0] >= 0x41 && buffer[0] <= 0x5a) ||
212
+ (buffer[0] >= 0x61 && buffer[0] <= 0x7a))) {
213
+ const char = String.fromCharCode(buffer[0]).toLowerCase();
214
+ const foundIndex = entries.findIndex((entry) => entry.displayName.toLowerCase().startsWith(char));
215
+ if (foundIndex !== -1) {
216
+ const page = Math.floor(foundIndex / PAGE_SIZE);
217
+ state = buildVisibleOptions(entries, page);
218
+ selectedIndex = foundIndex % PAGE_SIZE;
219
+ render();
220
+ }
221
+ else {
222
+ process.stdout.write('\x07');
223
+ }
224
+ }
225
+ };
226
+ input.on('data', onData);
227
+ render();
228
+ });
229
+ }
@@ -0,0 +1,59 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { colors } from './colors.js';
5
+ import { managerRoot, rootDir } from './env.js';
6
+ import { buildTsNodeRegisterImport } from './ts-node.js';
7
+ export function runEntry(entry, forwardedArgs) {
8
+ const detail = entry.script
9
+ ? path.relative(rootDir, entry.absoluteScript ?? entry.script)
10
+ : (entry.metaLabel ?? '[callback]');
11
+ console.log(`${entry.emoji} ${colors.green(`Running "${entry.displayName}"`)} ${colors.dim(detail)}`);
12
+ if (entry.handler) {
13
+ return Promise.resolve(entry.handler({ args: forwardedArgs, entry, rootDir }));
14
+ }
15
+ if (!entry.absoluteScript) {
16
+ throw new Error(`Script "${entry.displayName}" is missing a resolved path.`);
17
+ }
18
+ const scriptPath = entry.absoluteScript;
19
+ const tsConfigPath = path.join(rootDir, 'tsconfig.base.json');
20
+ const tsConfigFallback = path.join(rootDir, 'tsconfig.json');
21
+ const bundledTsconfig = path.join(managerRoot, 'tsconfig.base.json');
22
+ const projectPath = existsSync(tsConfigPath)
23
+ ? tsConfigPath
24
+ : existsSync(tsConfigFallback)
25
+ ? tsConfigFallback
26
+ : existsSync(bundledTsconfig)
27
+ ? bundledTsconfig
28
+ : undefined;
29
+ const extension = path.extname(scriptPath).toLowerCase();
30
+ const isTypeScript = extension === '.js' || extension === '.mts' || extension === '.cts';
31
+ const command = process.execPath;
32
+ const execArgs = isTypeScript
33
+ ? [
34
+ '--import',
35
+ buildTsNodeRegisterImport(scriptPath),
36
+ scriptPath,
37
+ ...forwardedArgs,
38
+ ]
39
+ : [scriptPath, ...forwardedArgs];
40
+ return new Promise((resolve, reject) => {
41
+ const child = spawn(command, execArgs, {
42
+ cwd: rootDir,
43
+ stdio: 'inherit',
44
+ env: isTypeScript
45
+ ? {
46
+ ...process.env,
47
+ ...(projectPath ? { TS_NODE_PROJECT: projectPath } : {}),
48
+ }
49
+ : process.env,
50
+ shell: process.platform === 'win32' && isTypeScript,
51
+ });
52
+ child.on('close', (code) => {
53
+ if (code === 0)
54
+ resolve();
55
+ else
56
+ reject(new Error(`Script "${entry.displayName}" exited with code ${code}`));
57
+ });
58
+ });
59
+ }