emoemu 0.1.0
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.
- package/.claude/settings.local.json +77 -0
- package/.node-version +1 -0
- package/CLAUDE.md +435 -0
- package/README.md +404 -0
- package/TODO.md +655 -0
- package/dist/index.cjs +25108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +25085 -0
- package/docs/building-libretro-cores-arm-mac.md +237 -0
- package/docs/config-file-format.md +488 -0
- package/docs/cores-trd.md +425 -0
- package/docs/headless-hardware-rendering-trd.md +676 -0
- package/docs/libretro-cores-trd.md +997 -0
- package/docs/mupen64-software-rendering-trd.md +751 -0
- package/docs/n64-support-trd.md +306 -0
- package/docs/native-rendering-trd.md +540 -0
- package/docs/native-ui-rendering-trd.md +1195 -0
- package/docs/netplay-trd.md +665 -0
- package/docs/retroarch-netplay-docs.md +277 -0
- package/docs/save-state-format.md +666 -0
- package/eslint.config.js +111 -0
- package/icon/icon.png +0 -0
- package/icon/icon.pxd +0 -0
- package/package.json +63 -0
- package/pnpm-workspace.yaml +10 -0
- package/src/Emulator/consts.ts +14 -0
- package/src/Emulator/index.ts +2496 -0
- package/src/Emulator/saveState/index.ts +155 -0
- package/src/Emulator/screenshot/index.ts +160 -0
- package/src/Emulator/terminalDimensions/index.ts +79 -0
- package/src/Emulator/types.ts +83 -0
- package/src/cli/commands/consts.ts +10 -0
- package/src/cli/commands/index.ts +462 -0
- package/src/cli/parseArgs/consts.ts +17 -0
- package/src/cli/parseArgs/index.ts +457 -0
- package/src/cli/parseArgs/types.ts +61 -0
- package/src/cli/runEmulator/index.ts +406 -0
- package/src/cli/runEmulator/types.ts +7 -0
- package/src/consts.ts +19 -0
- package/src/core/button/consts.ts +35 -0
- package/src/core/button/index.ts +123 -0
- package/src/core/core.ts +300 -0
- package/src/core/index.ts +19 -0
- package/src/cores/libretro/api/index.ts +334 -0
- package/src/cores/libretro/api/types.ts +148 -0
- package/src/cores/libretro/callbacks/consts.ts +41 -0
- package/src/cores/libretro/callbacks/index.ts +456 -0
- package/src/cores/libretro/consts.ts +45 -0
- package/src/cores/libretro/coreOptions/consts.ts +36 -0
- package/src/cores/libretro/coreOptions/index.ts +222 -0
- package/src/cores/libretro/environment/consts.ts +118 -0
- package/src/cores/libretro/environment/index.ts +1095 -0
- package/src/cores/libretro/index.ts +937 -0
- package/src/cores/libretro/loader/index.ts +496 -0
- package/src/cores/libretro/pixelFormat/consts.ts +43 -0
- package/src/cores/libretro/pixelFormat/index.ts +397 -0
- package/src/cores/libretro/types.ts +339 -0
- package/src/frontend/AudioManager/index.ts +420 -0
- package/src/frontend/SettingsManager/index.ts +250 -0
- package/src/frontend/config/index.ts +608 -0
- package/src/frontend/config/tests.ts +354 -0
- package/src/frontend/config/types.ts +36 -0
- package/src/frontend/consts.ts +114 -0
- package/src/frontend/coreBuilder/index.ts +644 -0
- package/src/frontend/coreBuilder/types.ts +15 -0
- package/src/frontend/coreDownloader/index.ts +620 -0
- package/src/frontend/coreDownloader/types.ts +17 -0
- package/src/frontend/corePreferences/index.ts +69 -0
- package/src/frontend/coreRegistry/index.ts +276 -0
- package/src/frontend/directoryCache/index.ts +75 -0
- package/src/frontend/index.ts +79 -0
- package/src/frontend/notifications/consts.ts +14 -0
- package/src/frontend/notifications/index.ts +250 -0
- package/src/frontend/playlist/consts.ts +168 -0
- package/src/frontend/playlist/index.ts +899 -0
- package/src/frontend/playlist/labelFormatter/consts.ts +15 -0
- package/src/frontend/playlist/labelFormatter/index.ts +155 -0
- package/src/frontend/playlist/labelFormatter/tests.ts +153 -0
- package/src/frontend/playlist/reader/index.ts +559 -0
- package/src/frontend/playlist/sync/index.ts +511 -0
- package/src/frontend/playlist/systemLookup/index.ts +233 -0
- package/src/frontend/playlist/utils/index.ts +50 -0
- package/src/frontend/romScanner/consts.ts +348 -0
- package/src/frontend/romScanner/index.ts +1957 -0
- package/src/frontend/saveServices/consts.ts +2 -0
- package/src/frontend/saveServices/index.ts +313 -0
- package/src/frontend/serviceProvider/index.ts +108 -0
- package/src/frontend/serviceProvider/types.ts +13 -0
- package/src/index.ts +428 -0
- package/src/input/Controller/consts.ts +50 -0
- package/src/input/Controller/index.ts +81 -0
- package/src/input/GamepadManager/consts.ts +22 -0
- package/src/input/GamepadManager/index.ts +418 -0
- package/src/input/InputManager/consts.ts +86 -0
- package/src/input/InputManager/index.ts +593 -0
- package/src/input/InputMapper/consts.ts +33 -0
- package/src/input/InputMapper/index.ts +436 -0
- package/src/input/consts.ts +410 -0
- package/src/input/gamepadProfiles/index.ts +1100 -0
- package/src/input/index.ts +38 -0
- package/src/input/inputUtils/index.ts +31 -0
- package/src/netplay/FrameBuffer/consts.ts +2 -0
- package/src/netplay/FrameBuffer/index.ts +364 -0
- package/src/netplay/FrameBuffer/tests.ts +286 -0
- package/src/netplay/InputBuffer/consts.ts +7 -0
- package/src/netplay/InputBuffer/index.ts +347 -0
- package/src/netplay/InputBuffer/tests.ts +166 -0
- package/src/netplay/NetplayClient/index.ts +976 -0
- package/src/netplay/NetplayConnection/index.ts +536 -0
- package/src/netplay/NetplayDiscovery/consts.ts +41 -0
- package/src/netplay/NetplayDiscovery/index.ts +525 -0
- package/src/netplay/NetplayServer/index.ts +1407 -0
- package/src/netplay/SyncManager/index.ts +984 -0
- package/src/netplay/SyncManager/tests.ts +419 -0
- package/src/netplay/consts.ts +371 -0
- package/src/netplay/crc32/consts.ts +14 -0
- package/src/netplay/crc32/index.ts +97 -0
- package/src/netplay/crc32/tests.ts +40 -0
- package/src/netplay/index.ts +41 -0
- package/src/netplay/netplayLogger/consts.ts +30 -0
- package/src/netplay/netplayLogger/index.ts +345 -0
- package/src/netplay/protocol/consts.ts +86 -0
- package/src/netplay/protocol/index.ts +1280 -0
- package/src/netplay/protocol/tests.ts +606 -0
- package/src/netplay/protocol/types.ts +20 -0
- package/src/netplay/types.ts +395 -0
- package/src/rendering/KittyRenderer/index.ts +616 -0
- package/src/rendering/NativeRenderer/index.ts +279 -0
- package/src/rendering/NativeRenderer/tests.ts +133 -0
- package/src/rendering/TerminalRenderer/index.ts +770 -0
- package/src/rendering/consts.ts +401 -0
- package/src/rendering/fonts/CozetteVector.ttf +0 -0
- package/src/rendering/index.ts +26 -0
- package/src/rendering/nativeUi/NativeWindowManager/index.ts +158 -0
- package/src/rendering/nativeUi/NativeWindowManager/tests.ts +81 -0
- package/src/rendering/nativeUi/consts.ts +6 -0
- package/src/rendering/nativeUi/index.ts +20 -0
- package/src/rendering/postProcessing/consts.ts +38 -0
- package/src/rendering/postProcessing/index.ts +923 -0
- package/src/rendering/shared/ansi/consts.ts +12 -0
- package/src/rendering/shared/ansi/index.ts +104 -0
- package/src/rendering/shared/consts.ts +2 -0
- package/src/rendering/shared/fitToTerminal/index.ts +67 -0
- package/src/ui/AddRomsPrompt/consts.ts +13 -0
- package/src/ui/AddRomsPrompt/index.tsx +781 -0
- package/src/ui/App/consts.ts +2 -0
- package/src/ui/App/index.tsx +456 -0
- package/src/ui/AppCapabilities/index.tsx +67 -0
- package/src/ui/ConfigContext/index.tsx +56 -0
- package/src/ui/CoreManager/consts.ts +11 -0
- package/src/ui/CoreManager/index.tsx +779 -0
- package/src/ui/CoreSelector/consts.ts +2 -0
- package/src/ui/CoreSelector/index.tsx +251 -0
- package/src/ui/DialogContainer/index.tsx +42 -0
- package/src/ui/DialogOptionsList/index.tsx +61 -0
- package/src/ui/DuplicateCrcPrompt/consts.ts +5 -0
- package/src/ui/DuplicateCrcPrompt/index.tsx +146 -0
- package/src/ui/GamepadContext/consts.ts +15 -0
- package/src/ui/GamepadContext/index.tsx +295 -0
- package/src/ui/NativeDialog/index.tsx +120 -0
- package/src/ui/NetplayDisconnectedDialog/index.tsx +93 -0
- package/src/ui/NetplayPauseMenu/consts.ts +2 -0
- package/src/ui/NetplayPauseMenu/index.tsx +97 -0
- package/src/ui/RomBrowser/NetplayPanel/consts.ts +24 -0
- package/src/ui/RomBrowser/NetplayPanel/index.tsx +520 -0
- package/src/ui/RomBrowser/SettingsPanel/index.tsx +478 -0
- package/src/ui/RomBrowser/consts.ts +61 -0
- package/src/ui/RomBrowser/index.tsx +1164 -0
- package/src/ui/RomBrowser/settingsConfig/index.ts +320 -0
- package/src/ui/RomBrowser/types.ts +67 -0
- package/src/ui/SaveStateDialog/consts.ts +2 -0
- package/src/ui/SaveStateDialog/index.tsx +225 -0
- package/src/ui/WarningDialog/index.tsx +113 -0
- package/src/ui/consts.ts +27 -0
- package/src/ui/hooks/useClearTerminal/consts.ts +2 -0
- package/src/ui/hooks/useClearTerminal/index.ts +37 -0
- package/src/ui/hooks/useDialogNavigation/index.ts +99 -0
- package/src/ui/hooks/useGamepad/consts.ts +21 -0
- package/src/ui/hooks/useGamepad/index.ts +194 -0
- package/src/ui/index.ts +27 -0
- package/src/utils/buffer/consts.ts +17 -0
- package/src/utils/buffer/index.ts +129 -0
- package/src/utils/color/consts.ts +58 -0
- package/src/utils/color/index.ts +183 -0
- package/src/utils/compression/consts.ts +50 -0
- package/src/utils/compression/index.ts +101 -0
- package/src/utils/consts.ts +2 -0
- package/src/utils/crc32/consts.ts +22 -0
- package/src/utils/crc32/index.ts +83 -0
- package/src/utils/ensureDirectory/index.ts +10 -0
- package/src/utils/format/consts.ts +8 -0
- package/src/utils/format/index.ts +53 -0
- package/src/utils/getErrorMessage/index.ts +10 -0
- package/src/utils/index.ts +113 -0
- package/src/utils/ini/index.ts +200 -0
- package/src/utils/kitty/consts.ts +13 -0
- package/src/utils/kitty/index.ts +181 -0
- package/src/utils/logger/consts.ts +35 -0
- package/src/utils/logger/index.ts +217 -0
- package/src/utils/paths/consts.ts +18 -0
- package/src/utils/paths/index.ts +151 -0
- package/src/utils/png/consts.ts +34 -0
- package/src/utils/png/index.ts +131 -0
- package/src/utils/readJsonFile/index.ts +16 -0
- package/src/utils/rotateLogFile/index.ts +44 -0
- package/src/utils/safeClose/index.ts +10 -0
- package/src/utils/terminal/consts.ts +8 -0
- package/src/utils/terminal/index.ts +102 -0
- package/src/utils/thumbnailRenderer/consts.ts +2 -0
- package/src/utils/thumbnailRenderer/index.ts +147 -0
- package/src/utils/typedError/index.ts +26 -0
- package/tsconfig.json +31 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { Instance } from 'ink';
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_TERMINAL_WIDTH,
|
|
4
|
+
DEFAULT_TERMINAL_HEIGHT,
|
|
5
|
+
INK_CLEANUP_DELAY_MS,
|
|
6
|
+
} from './consts';
|
|
7
|
+
|
|
8
|
+
export * from './consts';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get terminal dimensions with fallback defaults
|
|
12
|
+
*/
|
|
13
|
+
export const getTerminalDimensions = (): { width: number; height: number } => ({
|
|
14
|
+
width: process.stdout.columns || DEFAULT_TERMINAL_WIDTH,
|
|
15
|
+
height: process.stdout.rows || DEFAULT_TERMINAL_HEIGHT,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Clean up stdin after Ink releases it.
|
|
20
|
+
* Ink internally uses stdin in a way that can leave it in a bad state.
|
|
21
|
+
*/
|
|
22
|
+
export const cleanupStdin = (): void => {
|
|
23
|
+
process.stdin.removeAllListeners();
|
|
24
|
+
if (process.stdin.isTTY) {
|
|
25
|
+
process.stdin.setRawMode(false);
|
|
26
|
+
}
|
|
27
|
+
process.stdin.pause();
|
|
28
|
+
// Drain any buffered input
|
|
29
|
+
process.stdin.read();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Exit alternate screen buffer (used by some dialogs)
|
|
34
|
+
*/
|
|
35
|
+
export const exitAlternateScreen = (): void => {
|
|
36
|
+
process.stdout.write('\x1b[?1049l');
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Clean up an Ink instance and resolve a promise after cleanup.
|
|
41
|
+
* Handles unmounting, stdin cleanup, and a small delay to let stdin fully release.
|
|
42
|
+
*
|
|
43
|
+
* @param instance - The Ink render instance to clean up
|
|
44
|
+
* @param resolve - The promise resolve function to call after cleanup
|
|
45
|
+
* @param value - The value to resolve with
|
|
46
|
+
* @param options - Additional cleanup options
|
|
47
|
+
*/
|
|
48
|
+
export const cleanupInkInstance = <T>(
|
|
49
|
+
instance: Instance,
|
|
50
|
+
resolve: (value: T) => void,
|
|
51
|
+
value: T,
|
|
52
|
+
options: { exitAlternateScreen?: boolean } = {}
|
|
53
|
+
): void => {
|
|
54
|
+
instance.unmount();
|
|
55
|
+
|
|
56
|
+
if (options.exitAlternateScreen) {
|
|
57
|
+
exitAlternateScreen();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
cleanupStdin();
|
|
61
|
+
|
|
62
|
+
// Give stdin time to fully release
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
resolve(value);
|
|
65
|
+
}, INK_CLEANUP_DELAY_MS);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Display a message and wait for any key to be pressed.
|
|
70
|
+
* Useful for showing warnings/errors at startup before the main app clears the console.
|
|
71
|
+
*
|
|
72
|
+
* @param message - Optional custom message (defaults to "Press any key to continue...")
|
|
73
|
+
*/
|
|
74
|
+
export const pressAnyKeyToContinue = (message = "Press any key to continue..."): Promise<void> => {
|
|
75
|
+
return new Promise((resolve) => {
|
|
76
|
+
process.stdout.write(message);
|
|
77
|
+
|
|
78
|
+
// Configure stdin for single keypress detection
|
|
79
|
+
const isTTY = process.stdin.isTTY;
|
|
80
|
+
const wasRaw = isTTY ? process.stdin.isRaw : false;
|
|
81
|
+
if (isTTY) {
|
|
82
|
+
process.stdin.setRawMode(true);
|
|
83
|
+
}
|
|
84
|
+
process.stdin.resume();
|
|
85
|
+
|
|
86
|
+
const onData = (): void => {
|
|
87
|
+
// Restore stdin state
|
|
88
|
+
process.stdin.removeListener('data', onData);
|
|
89
|
+
if (isTTY) {
|
|
90
|
+
process.stdin.setRawMode(wasRaw);
|
|
91
|
+
}
|
|
92
|
+
process.stdin.pause();
|
|
93
|
+
|
|
94
|
+
// Move to next line after the prompt
|
|
95
|
+
process.stdout.write('\n');
|
|
96
|
+
|
|
97
|
+
resolve();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
process.stdin.once('data', onData);
|
|
101
|
+
});
|
|
102
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Half-block Thumbnail Renderer
|
|
3
|
+
*
|
|
4
|
+
* Renders thumbnails using Unicode half-block characters (▀) for terminals
|
|
5
|
+
* that don't support the Kitty graphics protocol. Each character represents
|
|
6
|
+
* 2 vertical pixels using foreground (top) and background (bottom) colors.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import sharp from 'sharp';
|
|
10
|
+
import { fgTrueColor, bgTrueColor, HALF_BLOCK_TOP, RESET, moveCursor } from '../../rendering/shared/ansi';
|
|
11
|
+
|
|
12
|
+
export * from './consts';
|
|
13
|
+
|
|
14
|
+
import { RGB_CHANNELS } from './consts';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Rendered thumbnail result with rows of ANSI-colored half-block characters.
|
|
18
|
+
*/
|
|
19
|
+
export interface RenderedThumbnail {
|
|
20
|
+
/** Array of strings, one per terminal row */
|
|
21
|
+
rows: string[];
|
|
22
|
+
/** Width in terminal columns */
|
|
23
|
+
width: number;
|
|
24
|
+
/** Height in terminal rows */
|
|
25
|
+
height: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Decode a base64 PNG and render it as half-block characters.
|
|
30
|
+
*
|
|
31
|
+
* @param base64Data - Base64-encoded PNG image data
|
|
32
|
+
* @param targetCols - Target width in terminal columns
|
|
33
|
+
* @param targetRows - Target height in terminal rows (each row = 2 pixels)
|
|
34
|
+
* @returns Promise resolving to rendered thumbnail or undefined on error
|
|
35
|
+
*/
|
|
36
|
+
export const renderThumbnailHalfBlocks = async (
|
|
37
|
+
base64Data: string,
|
|
38
|
+
targetCols: number,
|
|
39
|
+
targetRows: number
|
|
40
|
+
): Promise<RenderedThumbnail | undefined> => {
|
|
41
|
+
try {
|
|
42
|
+
const buffer = Buffer.from(base64Data, 'base64');
|
|
43
|
+
|
|
44
|
+
// Target pixel dimensions: width = cols, height = rows * 2 (half-blocks)
|
|
45
|
+
const targetWidth = targetCols;
|
|
46
|
+
const targetHeight = targetRows * 2;
|
|
47
|
+
|
|
48
|
+
// Decode and resize PNG to target dimensions
|
|
49
|
+
const { data, info } = await sharp(buffer)
|
|
50
|
+
.resize(targetWidth, targetHeight, {
|
|
51
|
+
fit: 'contain',
|
|
52
|
+
background: { r: 0, g: 0, b: 0, alpha: 1 },
|
|
53
|
+
})
|
|
54
|
+
.removeAlpha()
|
|
55
|
+
.raw()
|
|
56
|
+
.toBuffer({ resolveWithObject: true });
|
|
57
|
+
|
|
58
|
+
const { width, height } = info;
|
|
59
|
+
const rows: string[] = [];
|
|
60
|
+
|
|
61
|
+
// Process 2 rows of pixels at a time to create half-block characters
|
|
62
|
+
for (let y = 0; y < height; y += 2) {
|
|
63
|
+
let row = '';
|
|
64
|
+
|
|
65
|
+
for (let x = 0; x < width; x++) {
|
|
66
|
+
// Top pixel (foreground)
|
|
67
|
+
const topIdx = (y * width + x) * RGB_CHANNELS;
|
|
68
|
+
const topR = data[topIdx];
|
|
69
|
+
const topG = data[topIdx + 1];
|
|
70
|
+
const topB = data[topIdx + 2];
|
|
71
|
+
|
|
72
|
+
// Bottom pixel (background) - may not exist if height is odd
|
|
73
|
+
const bottomY = y + 1;
|
|
74
|
+
let bottomR = 0, bottomG = 0, bottomB = 0;
|
|
75
|
+
|
|
76
|
+
if (bottomY < height) {
|
|
77
|
+
const bottomIdx = (bottomY * width + x) * RGB_CHANNELS;
|
|
78
|
+
bottomR = data[bottomIdx];
|
|
79
|
+
bottomG = data[bottomIdx + 1];
|
|
80
|
+
bottomB = data[bottomIdx + 2];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
row += fgTrueColor(topR, topG, topB) +
|
|
84
|
+
bgTrueColor(bottomR, bottomG, bottomB) +
|
|
85
|
+
HALF_BLOCK_TOP;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
row += RESET;
|
|
89
|
+
rows.push(row);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
rows,
|
|
94
|
+
width,
|
|
95
|
+
height: rows.length,
|
|
96
|
+
};
|
|
97
|
+
} catch {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Build ANSI escape sequence to render a half-block thumbnail at a specific position.
|
|
104
|
+
*
|
|
105
|
+
* @param thumbnail - Rendered thumbnail from renderThumbnailHalfBlocks
|
|
106
|
+
* @param startRow - Starting row (1-based)
|
|
107
|
+
* @param startCol - Starting column (1-based)
|
|
108
|
+
* @returns Complete escape sequence string to render the thumbnail
|
|
109
|
+
*/
|
|
110
|
+
export const buildThumbnailSequence = (
|
|
111
|
+
thumbnail: RenderedThumbnail,
|
|
112
|
+
startRow: number,
|
|
113
|
+
startCol: number
|
|
114
|
+
): string => {
|
|
115
|
+
let output = '';
|
|
116
|
+
|
|
117
|
+
for (let i = 0; i < thumbnail.rows.length; i++) {
|
|
118
|
+
output += moveCursor(startRow + i, startCol) + thumbnail.rows[i];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return output;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build ANSI escape sequence to clear a thumbnail area (fill with spaces).
|
|
126
|
+
*
|
|
127
|
+
* @param width - Width in columns
|
|
128
|
+
* @param height - Height in rows
|
|
129
|
+
* @param startRow - Starting row (1-based)
|
|
130
|
+
* @param startCol - Starting column (1-based)
|
|
131
|
+
* @returns Escape sequence to clear the area
|
|
132
|
+
*/
|
|
133
|
+
export const buildThumbnailClearSequence = (
|
|
134
|
+
width: number,
|
|
135
|
+
height: number,
|
|
136
|
+
startRow: number,
|
|
137
|
+
startCol: number
|
|
138
|
+
): string => {
|
|
139
|
+
let output = '';
|
|
140
|
+
const spaces = ' '.repeat(width);
|
|
141
|
+
|
|
142
|
+
for (let i = 0; i < height; i++) {
|
|
143
|
+
output += moveCursor(startRow + i, startCol) + spaces;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return output;
|
|
147
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory for creating typed error classes with machine-readable error codes.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ```typescript
|
|
6
|
+
* type MyErrorCode = 'NOT_FOUND' | 'TIMEOUT';
|
|
7
|
+
* const { TypedError: MyError, isTypedError: isMyError } = createTypedError<MyErrorCode>('MyError');
|
|
8
|
+
* throw new MyError('NOT_FOUND', 'Resource not found');
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export const createTypedError = <T extends string>(name: string) => {
|
|
12
|
+
class TypedError extends Error {
|
|
13
|
+
readonly code: T;
|
|
14
|
+
constructor(code: T, details?: string) {
|
|
15
|
+
super(details ? `${code}: ${details}` : code);
|
|
16
|
+
this.name = name;
|
|
17
|
+
this.code = code;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const isTypedError = (error: unknown): error is TypedError => {
|
|
22
|
+
return error instanceof TypedError;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return { TypedError, isTypedError };
|
|
26
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"baseUrl": ".",
|
|
7
|
+
"paths": {
|
|
8
|
+
"@/*": ["src/*"]
|
|
9
|
+
},
|
|
10
|
+
"lib": ["ES2022"],
|
|
11
|
+
"outDir": "dist",
|
|
12
|
+
"rootDir": "src",
|
|
13
|
+
"strict": true,
|
|
14
|
+
"esModuleInterop": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"forceConsistentCasingInFileNames": true,
|
|
17
|
+
"resolveJsonModule": true,
|
|
18
|
+
"declaration": true,
|
|
19
|
+
"declarationMap": true,
|
|
20
|
+
"sourceMap": true,
|
|
21
|
+
"jsx": "react-jsx",
|
|
22
|
+
"noUnusedLocals": true,
|
|
23
|
+
"noUnusedParameters": true,
|
|
24
|
+
"noImplicitReturns": true,
|
|
25
|
+
"noFallthroughCasesInSwitch": true,
|
|
26
|
+
"allowImportingTsExtensions": true,
|
|
27
|
+
"noEmit": true
|
|
28
|
+
},
|
|
29
|
+
"include": ["src/**/*"],
|
|
30
|
+
"exclude": ["node_modules", "dist"]
|
|
31
|
+
}
|
package/vitest.config.ts
ADDED