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,644 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Builder Service
|
|
3
|
+
*
|
|
4
|
+
* Builds libretro cores from source for platforms where pre-built
|
|
5
|
+
* binaries are not available or require specific build options.
|
|
6
|
+
*
|
|
7
|
+
* Currently supports:
|
|
8
|
+
* - mupen64plus_next on ARM macOS (requires software rendering build)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync, spawn } from "child_process";
|
|
12
|
+
import { existsSync, rmSync, copyFileSync, readFileSync, writeFileSync } from "fs";
|
|
13
|
+
import { ensureDirectory } from '../../utils/ensureDirectory';
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
import { platform, arch, tmpdir } from "os";
|
|
16
|
+
import { getCoresDirectory } from "../config";
|
|
17
|
+
import { logger } from "../../utils/logger";
|
|
18
|
+
import { getErrorMessage } from "../../utils/getErrorMessage";
|
|
19
|
+
import { CoreBuildError } from "./types";
|
|
20
|
+
|
|
21
|
+
/** Default number of CPU cores to use for parallel builds */
|
|
22
|
+
const DEFAULT_CPU_COUNT = 4;
|
|
23
|
+
|
|
24
|
+
/** Number of recent output lines to show on build error */
|
|
25
|
+
const ERROR_OUTPUT_LINES = 10;
|
|
26
|
+
|
|
27
|
+
/** Bytes per kilobyte */
|
|
28
|
+
const BYTES_PER_KB = 1024;
|
|
29
|
+
|
|
30
|
+
/** Bytes per megabyte */
|
|
31
|
+
const BYTES_PER_MB = BYTES_PER_KB * BYTES_PER_KB;
|
|
32
|
+
|
|
33
|
+
/** Buffer size multiplier for make dry run */
|
|
34
|
+
const MAKE_DRY_RUN_BUFFER_MB = 10;
|
|
35
|
+
|
|
36
|
+
/** Buffer size for make dry run (10MB to handle large projects) */
|
|
37
|
+
const MAKE_DRY_RUN_BUFFER_SIZE = MAKE_DRY_RUN_BUFFER_MB * BYTES_PER_MB;
|
|
38
|
+
|
|
39
|
+
/** Length of short git commit hash for display purposes */
|
|
40
|
+
const SHORT_COMMIT_HASH_LENGTH = 8;
|
|
41
|
+
|
|
42
|
+
/** Build progress phases */
|
|
43
|
+
export type BuildPhase = "cloning" | "building" | "installing" | "complete" | "error";
|
|
44
|
+
|
|
45
|
+
/** Progress callback for build operations */
|
|
46
|
+
export interface BuildProgress {
|
|
47
|
+
phase: BuildPhase;
|
|
48
|
+
message: string;
|
|
49
|
+
/** Output lines from the build process */
|
|
50
|
+
output?: string[];
|
|
51
|
+
/** Progress percentage (0-100) if available */
|
|
52
|
+
progressPercent?: number;
|
|
53
|
+
/** Human-readable progress text (e.g., "15 of 238 files") */
|
|
54
|
+
progressText?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Core build configuration */
|
|
58
|
+
interface CoreBuildConfig {
|
|
59
|
+
/** Git repository URL */
|
|
60
|
+
repo: string;
|
|
61
|
+
/** Specific commit hash to checkout (ensures reproducible builds and stable patches) */
|
|
62
|
+
commit: string;
|
|
63
|
+
/** Build command (uses make by default) */
|
|
64
|
+
buildArgs: string[];
|
|
65
|
+
/** Output filename after build */
|
|
66
|
+
outputFile: string;
|
|
67
|
+
/** Installed filename */
|
|
68
|
+
installedFile: string;
|
|
69
|
+
/** Human-readable description */
|
|
70
|
+
description: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Cores that require building from source on specific platforms */
|
|
74
|
+
const BUILD_CONFIGS: Partial<Record<string, CoreBuildConfig>> = {
|
|
75
|
+
mupen64plus_next: {
|
|
76
|
+
repo: "https://github.com/libretro/mupen64plus-libretro-nx.git",
|
|
77
|
+
// Pin to specific commit for reproducible builds and stable patches
|
|
78
|
+
// Last verified: 2025-01-25 (patches for pngpriv.h and zutil.h tested against this commit)
|
|
79
|
+
commit: "bc43bcedc276861254b48526f56799d63a30723b",
|
|
80
|
+
buildArgs: [
|
|
81
|
+
"platform=osx",
|
|
82
|
+
"HAVE_PARALLEL_RDP=0", // Disable Vulkan-dependent ParaLLEl RDP
|
|
83
|
+
"HAVE_PARALLEL_RSP=1", // Enable fast RSP dynarec (required for Angrylion)
|
|
84
|
+
"HAVE_THR_AL=1", // Enable Angrylion multi-threading
|
|
85
|
+
"LLE=1", // Enable low-level emulation
|
|
86
|
+
// Note: WITH_DYNAREC is intentionally omitted - the ARM64 dynarec assembly
|
|
87
|
+
// uses GNU syntax (.hidden, .type) that's incompatible with macOS's assembler.
|
|
88
|
+
// The Makefile defaults to interpreter mode on macOS, which is slower but
|
|
89
|
+
// compatible. Parallel RSP still uses its own dynarec which works.
|
|
90
|
+
],
|
|
91
|
+
outputFile: "mupen64plus_next_libretro.dylib",
|
|
92
|
+
installedFile: "mupen64plus_next_libretro.dylib",
|
|
93
|
+
description: "Nintendo 64 (Mupen64Plus-Next with Angrylion software renderer)",
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Stub OpenGL library source code
|
|
99
|
+
*
|
|
100
|
+
* The mupen64plus core links against OpenGL even when using the Angrylion
|
|
101
|
+
* software renderer. On macOS, loading the real OpenGL framework causes
|
|
102
|
+
* hangs during library initialization (likely GPU probing).
|
|
103
|
+
*
|
|
104
|
+
* This stub provides empty implementations of the OpenGL functions needed
|
|
105
|
+
* to satisfy the linker. Since we use Angrylion (software rendering),
|
|
106
|
+
* these functions are never actually called at runtime.
|
|
107
|
+
*/
|
|
108
|
+
const GL_STUB_SOURCE = `
|
|
109
|
+
// Stub OpenGL functions to allow linking without the real OpenGL framework
|
|
110
|
+
// These functions will never be called at runtime when using Angrylion
|
|
111
|
+
|
|
112
|
+
typedef unsigned int GLenum;
|
|
113
|
+
typedef unsigned int GLuint;
|
|
114
|
+
typedef int GLint;
|
|
115
|
+
typedef int GLsizei;
|
|
116
|
+
typedef float GLfloat;
|
|
117
|
+
typedef double GLdouble;
|
|
118
|
+
typedef unsigned char GLboolean;
|
|
119
|
+
typedef void GLvoid;
|
|
120
|
+
typedef unsigned char GLubyte;
|
|
121
|
+
|
|
122
|
+
void glBindTexture(GLenum target, GLuint texture) {}
|
|
123
|
+
void glBlendFunc(GLenum sfactor, GLenum dfactor) {}
|
|
124
|
+
void glClear(GLuint mask) {}
|
|
125
|
+
void glClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) {}
|
|
126
|
+
void glClearDepth(GLdouble depth) {}
|
|
127
|
+
void glColorMask(GLboolean r, GLboolean g, GLboolean b, GLboolean a) {}
|
|
128
|
+
void glCullFace(GLenum mode) {}
|
|
129
|
+
void glDeleteTextures(GLsizei n, const GLuint *textures) {}
|
|
130
|
+
void glDepthFunc(GLenum func) {}
|
|
131
|
+
void glDepthMask(GLboolean flag) {}
|
|
132
|
+
void glDepthRange(GLdouble near, GLdouble far) {}
|
|
133
|
+
void glDisable(GLenum cap) {}
|
|
134
|
+
void glDrawArrays(GLenum mode, GLint first, GLsizei count) {}
|
|
135
|
+
void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices) {}
|
|
136
|
+
void glEnable(GLenum cap) {}
|
|
137
|
+
void glFinish(void) {}
|
|
138
|
+
void glFrontFace(GLenum mode) {}
|
|
139
|
+
void glGenTextures(GLsizei n, GLuint *textures) {}
|
|
140
|
+
GLenum glGetError(void) { return 0; }
|
|
141
|
+
void glGetFloatv(GLenum pname, GLfloat *params) {}
|
|
142
|
+
void glGetIntegerv(GLenum pname, GLint *params) {}
|
|
143
|
+
const GLubyte* glGetString(GLenum name) { return (const GLubyte*)""; }
|
|
144
|
+
void glLineWidth(GLfloat width) {}
|
|
145
|
+
void glPixelStorei(GLenum pname, GLint param) {}
|
|
146
|
+
void glPolygonMode(GLenum face, GLenum mode) {}
|
|
147
|
+
void glPolygonOffset(GLfloat factor, GLfloat units) {}
|
|
148
|
+
void glReadBuffer(GLenum mode) {}
|
|
149
|
+
void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels) {}
|
|
150
|
+
void glScissor(GLint x, GLint y, GLsizei width, GLsizei height) {}
|
|
151
|
+
void glStencilFunc(GLenum func, GLint ref, GLuint mask) {}
|
|
152
|
+
void glStencilMask(GLuint mask) {}
|
|
153
|
+
void glStencilOp(GLenum fail, GLenum zfail, GLenum zpass) {}
|
|
154
|
+
void glTexParameteri(GLenum target, GLenum pname, GLint param) {}
|
|
155
|
+
void glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) {}
|
|
156
|
+
void glViewport(GLint x, GLint y, GLsizei width, GLsizei height) {}
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
/** Name of the stub OpenGL library */
|
|
160
|
+
const GL_STUB_LIB_NAME = "libGL_stub.dylib";
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Create a stub OpenGL library for macOS
|
|
164
|
+
*
|
|
165
|
+
* This allows mupen64plus to link without the real OpenGL framework,
|
|
166
|
+
* avoiding the library loading hang on macOS.
|
|
167
|
+
*
|
|
168
|
+
* @param outputDir Directory to create the stub library in
|
|
169
|
+
* @returns Path to the created stub library
|
|
170
|
+
*/
|
|
171
|
+
const createGLStubLibrary = (outputDir: string): string => {
|
|
172
|
+
const stubSourcePath = join(outputDir, "gl_stubs.c");
|
|
173
|
+
const stubLibPath = join(outputDir, GL_STUB_LIB_NAME);
|
|
174
|
+
|
|
175
|
+
// Write stub source
|
|
176
|
+
writeFileSync(stubSourcePath, GL_STUB_SOURCE, "utf-8");
|
|
177
|
+
|
|
178
|
+
// Compile stub library
|
|
179
|
+
execSync(
|
|
180
|
+
`clang -dynamiclib -o "${stubLibPath}" "${stubSourcePath}" -install_name @rpath/${GL_STUB_LIB_NAME}`,
|
|
181
|
+
{ cwd: outputDir }
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
logger.info(`Created stub OpenGL library: ${stubLibPath}`, "CoreBuilder");
|
|
185
|
+
|
|
186
|
+
return stubLibPath;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Fix library paths in the built core to use the stub library
|
|
191
|
+
*
|
|
192
|
+
* @param corePath Path to the built core dylib
|
|
193
|
+
* @param coresDir Directory where the stub library is installed
|
|
194
|
+
*/
|
|
195
|
+
const fixLibraryPaths = (corePath: string, coresDir: string): void => {
|
|
196
|
+
const stubLibPath = join(coresDir, GL_STUB_LIB_NAME);
|
|
197
|
+
|
|
198
|
+
// Change the @rpath reference to an absolute path
|
|
199
|
+
execSync(
|
|
200
|
+
`install_name_tool -change "@rpath/${GL_STUB_LIB_NAME}" "${stubLibPath}" "${corePath}"`,
|
|
201
|
+
{ stdio: "pipe" }
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
logger.info(`Fixed library paths in ${corePath}`, "CoreBuilder");
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Apply source patches needed for modern macOS compatibility
|
|
209
|
+
*
|
|
210
|
+
* The bundled libpng and libzlib have outdated macOS compatibility checks
|
|
211
|
+
* that assume Classic Mac OS (pre-OS X) rather than modern macOS.
|
|
212
|
+
*
|
|
213
|
+
* Issues fixed:
|
|
214
|
+
* 1. libpng's pngpriv.h includes <fp.h> which was removed in modern macOS SDKs
|
|
215
|
+
* 2. libzlib's zutil.h defines fdopen() as NULL for TARGET_OS_MAC, but modern
|
|
216
|
+
* macOS has fdopen() available - this breaks when _stdio.h is included
|
|
217
|
+
*/
|
|
218
|
+
const applySourcePatches = (repoDir: string): void => {
|
|
219
|
+
// Patch 1: Fix libpng fp.h issue
|
|
220
|
+
const pngPrivPath = join(repoDir, "custom/dependencies/libpng/pngpriv.h");
|
|
221
|
+
if (existsSync(pngPrivPath)) {
|
|
222
|
+
let content = readFileSync(pngPrivPath, "utf-8");
|
|
223
|
+
const fpHPattern = /#\s*include\s*<fp\.h>/g;
|
|
224
|
+
if (fpHPattern.test(content)) {
|
|
225
|
+
content = content.replace(fpHPattern, "# include <math.h>");
|
|
226
|
+
writeFileSync(pngPrivPath, content, "utf-8");
|
|
227
|
+
logger.info("Patched pngpriv.h: replaced <fp.h> with <math.h>", "CoreBuilder");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Patch 2: Fix libzlib fdopen issue
|
|
232
|
+
// The bundled zlib defines fdopen(fd,mode) as NULL for TARGET_OS_MAC,
|
|
233
|
+
// assuming Classic Mac OS. Modern macOS has fdopen(), and the NULL macro
|
|
234
|
+
// breaks when _stdio.h declares the real fdopen function.
|
|
235
|
+
//
|
|
236
|
+
// Fix: Add a check for __APPLE__ to skip the NULL definition on modern macOS
|
|
237
|
+
const zlibUtilPath = join(repoDir, "custom/dependencies/libzlib/zutil.h");
|
|
238
|
+
if (existsSync(zlibUtilPath)) {
|
|
239
|
+
let content = readFileSync(zlibUtilPath, "utf-8");
|
|
240
|
+
|
|
241
|
+
// The problematic code block looks like:
|
|
242
|
+
// #if defined(MACOS) || defined(TARGET_OS_MAC)
|
|
243
|
+
// ...
|
|
244
|
+
// #ifndef fdopen
|
|
245
|
+
// #define fdopen(fd,mode) NULL /* No fdopen() */
|
|
246
|
+
// #endif
|
|
247
|
+
//
|
|
248
|
+
// We need to exclude modern macOS (__APPLE__) from this condition
|
|
249
|
+
const oldCondition = "#if defined(MACOS) || defined(TARGET_OS_MAC)";
|
|
250
|
+
const newCondition = "#if (defined(MACOS) || defined(TARGET_OS_MAC)) && !defined(__APPLE__)";
|
|
251
|
+
|
|
252
|
+
if (content.includes(oldCondition)) {
|
|
253
|
+
content = content.replace(oldCondition, newCondition);
|
|
254
|
+
writeFileSync(zlibUtilPath, content, "utf-8");
|
|
255
|
+
logger.info("Patched zutil.h: excluded modern macOS from fdopen NULL definition", "CoreBuilder");
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Check if a core requires building from source on the current platform
|
|
262
|
+
*/
|
|
263
|
+
export const requiresBuildFromSource = (coreName: string): boolean => {
|
|
264
|
+
// Only ARM macOS requires building certain cores
|
|
265
|
+
if (platform() !== "darwin" || arch() !== "arm64") {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return coreName in BUILD_CONFIGS;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Get description of why a core needs to be built
|
|
274
|
+
*/
|
|
275
|
+
export const getBuildReason = (coreName: string): string | null => {
|
|
276
|
+
if (!requiresBuildFromSource(coreName)) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (coreName === "mupen64plus_next") {
|
|
281
|
+
return "The pre-built N64 core for ARM Mac requires OpenGL. Building from source with Angrylion software renderer enabled.";
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return "Pre-built binary not available for ARM Mac.";
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Check if required build tools are available
|
|
289
|
+
*/
|
|
290
|
+
export const checkBuildPrerequisites = (): { ok: boolean; missing: string[] } => {
|
|
291
|
+
const required = ["git", "make", "clang"];
|
|
292
|
+
const missing: string[] = [];
|
|
293
|
+
|
|
294
|
+
for (const tool of required) {
|
|
295
|
+
try {
|
|
296
|
+
execSync(`which ${tool}`, { stdio: "pipe" });
|
|
297
|
+
} catch {
|
|
298
|
+
missing.push(tool);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return { ok: missing.length === 0, missing };
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get the number of CPU cores for parallel builds
|
|
307
|
+
*/
|
|
308
|
+
const getCpuCount = (): number => {
|
|
309
|
+
try {
|
|
310
|
+
const result = execSync("sysctl -n hw.ncpu", { encoding: "utf-8" });
|
|
311
|
+
return parseInt(result.trim(), 10) || DEFAULT_CPU_COUNT;
|
|
312
|
+
} catch {
|
|
313
|
+
return DEFAULT_CPU_COUNT;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Patterns that indicate a compilation step in make output
|
|
319
|
+
* These are common patterns from C/C++ builds
|
|
320
|
+
*/
|
|
321
|
+
const COMPILATION_PATTERNS = [
|
|
322
|
+
/^\s*(CC|CXX|COMPILE|Compiling)\s/i, // GCC/Clang style
|
|
323
|
+
/^\s*\[\s*\d+%\s*\]\s*(Building|Compiling)/i, // CMake style
|
|
324
|
+
/^(cc|c\+\+|gcc|g\+\+|clang|clang\+\+)\s/i, // Direct compiler invocation
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Check if a line represents a compilation step
|
|
329
|
+
*/
|
|
330
|
+
const isCompilationLine = (line: string): boolean => {
|
|
331
|
+
return COMPILATION_PATTERNS.some(pattern => pattern.test(line));
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Count the number of compilation targets in a make dry run output
|
|
336
|
+
*/
|
|
337
|
+
const countCompilationTargets = (output: string): number => {
|
|
338
|
+
const lines = output.split("\n");
|
|
339
|
+
let count = 0;
|
|
340
|
+
for (const line of lines) {
|
|
341
|
+
if (isCompilationLine(line)) {
|
|
342
|
+
count++;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return count;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Get the total number of compilation steps by doing a make dry run
|
|
350
|
+
*/
|
|
351
|
+
const getMakeTargetCount = (cwd: string, makeArgs: string[]): number | null => {
|
|
352
|
+
try {
|
|
353
|
+
// -n does a dry run (prints commands without executing)
|
|
354
|
+
const result = execSync(`make -n ${makeArgs.join(" ")}`, {
|
|
355
|
+
cwd,
|
|
356
|
+
encoding: "utf-8",
|
|
357
|
+
maxBuffer: MAKE_DRY_RUN_BUFFER_SIZE,
|
|
358
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
359
|
+
});
|
|
360
|
+
const count = countCompilationTargets(result);
|
|
361
|
+
logger.info(`Counted ${count} compilation targets from make dry run`, "CoreBuilder");
|
|
362
|
+
return count > 0 ? count : null;
|
|
363
|
+
} catch {
|
|
364
|
+
// Dry run failed, continue without progress tracking
|
|
365
|
+
logger.warn("Make dry run failed, build progress will not be tracked", "CoreBuilder");
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Build a core from source
|
|
372
|
+
*
|
|
373
|
+
* @param coreName Name of the core to build
|
|
374
|
+
* @param onProgress Optional callback for progress updates
|
|
375
|
+
* @returns Path to the installed core file
|
|
376
|
+
*/
|
|
377
|
+
export const buildCore = async (
|
|
378
|
+
coreName: string,
|
|
379
|
+
onProgress?: (progress: BuildProgress) => void
|
|
380
|
+
): Promise<string> => {
|
|
381
|
+
const config = BUILD_CONFIGS[coreName];
|
|
382
|
+
if (!config) {
|
|
383
|
+
throw new CoreBuildError('NO_BUILD_CONFIG', coreName);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Check prerequisites
|
|
387
|
+
const prereqs = checkBuildPrerequisites();
|
|
388
|
+
if (!prereqs.ok) {
|
|
389
|
+
throw new CoreBuildError('MISSING_TOOLS', prereqs.missing.join(', '));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const coresDir = getCoresDirectory();
|
|
393
|
+
const destPath = join(coresDir, config.installedFile);
|
|
394
|
+
|
|
395
|
+
// Check if already exists
|
|
396
|
+
if (existsSync(destPath)) {
|
|
397
|
+
onProgress?.({ phase: "complete", message: "Core already installed" });
|
|
398
|
+
return destPath;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
ensureDirectory(coresDir);
|
|
402
|
+
|
|
403
|
+
// Create temp directory for build
|
|
404
|
+
const buildDir = join(tmpdir(), `emoemu-build-${coreName}-${Date.now()}`);
|
|
405
|
+
ensureDirectory(buildDir);
|
|
406
|
+
|
|
407
|
+
const repoDir = join(buildDir, coreName);
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
// Clone repository at specific commit for reproducible builds
|
|
411
|
+
onProgress?.({
|
|
412
|
+
phase: "cloning",
|
|
413
|
+
message: `Cloning ${config.repo} at ${config.commit.slice(0, SHORT_COMMIT_HASH_LENGTH)}...`,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
logger.info(`Cloning ${config.repo} at commit ${config.commit} to ${repoDir}`, "CoreBuilder");
|
|
417
|
+
|
|
418
|
+
// Use git init + fetch + checkout to get only the specific commit
|
|
419
|
+
// This is more efficient than cloning everything and ensures reproducibility
|
|
420
|
+
ensureDirectory(repoDir);
|
|
421
|
+
|
|
422
|
+
await runCommand("git", ["init"], {
|
|
423
|
+
cwd: repoDir,
|
|
424
|
+
onProgress,
|
|
425
|
+
phase: "cloning",
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
await runCommand("git", ["remote", "add", "origin", config.repo], {
|
|
429
|
+
cwd: repoDir,
|
|
430
|
+
onProgress,
|
|
431
|
+
phase: "cloning",
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
await runCommand("git", ["fetch", "--depth", "1", "origin", config.commit], {
|
|
435
|
+
cwd: repoDir,
|
|
436
|
+
onProgress,
|
|
437
|
+
phase: "cloning",
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
await runCommand("git", ["checkout", "FETCH_HEAD"], {
|
|
441
|
+
cwd: repoDir,
|
|
442
|
+
onProgress,
|
|
443
|
+
phase: "cloning",
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// Apply source patches for macOS compatibility
|
|
447
|
+
applySourcePatches(repoDir);
|
|
448
|
+
|
|
449
|
+
// Create stub OpenGL library for mupen64plus (avoids macOS OpenGL loading hang)
|
|
450
|
+
let stubLibPath: string | null = null;
|
|
451
|
+
if (coreName === "mupen64plus_next") {
|
|
452
|
+
onProgress?.({
|
|
453
|
+
phase: "building",
|
|
454
|
+
message: "Creating stub OpenGL library...",
|
|
455
|
+
});
|
|
456
|
+
stubLibPath = createGLStubLibrary(buildDir);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Build
|
|
460
|
+
onProgress?.({
|
|
461
|
+
phase: "building",
|
|
462
|
+
message: `Building ${config.description}...`,
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const cpuCount = getCpuCount();
|
|
466
|
+
const baseArgs = ["-j" + cpuCount, ...config.buildArgs];
|
|
467
|
+
|
|
468
|
+
// Add stub library to link args for mupen64plus
|
|
469
|
+
const makeArgs = (stubLibPath && coreName === "mupen64plus_next")
|
|
470
|
+
? [...baseArgs, `GL_LIB=-L${buildDir} -lGL_stub`]
|
|
471
|
+
: baseArgs;
|
|
472
|
+
|
|
473
|
+
logger.info(`Building with: make ${makeArgs.join(" ")}`, "CoreBuilder");
|
|
474
|
+
|
|
475
|
+
// Count compilation targets for progress tracking
|
|
476
|
+
onProgress?.({
|
|
477
|
+
phase: "building",
|
|
478
|
+
message: "Analyzing build targets...",
|
|
479
|
+
});
|
|
480
|
+
const totalTargets = getMakeTargetCount(repoDir, makeArgs);
|
|
481
|
+
|
|
482
|
+
await runCommand("make", makeArgs, {
|
|
483
|
+
cwd: repoDir,
|
|
484
|
+
onProgress,
|
|
485
|
+
phase: "building",
|
|
486
|
+
totalTargets,
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// Install
|
|
490
|
+
onProgress?.({
|
|
491
|
+
phase: "installing",
|
|
492
|
+
message: "Installing core...",
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const builtPath = join(repoDir, config.outputFile);
|
|
496
|
+
if (!existsSync(builtPath)) {
|
|
497
|
+
throw new CoreBuildError('OUTPUT_NOT_FOUND', builtPath);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Copy stub library first (needed before fixing library paths)
|
|
501
|
+
if (stubLibPath && coreName === "mupen64plus_next") {
|
|
502
|
+
const stubDestPath = join(coresDir, GL_STUB_LIB_NAME);
|
|
503
|
+
copyFileSync(stubLibPath, stubDestPath);
|
|
504
|
+
execSync(`chmod 755 "${stubDestPath}"`);
|
|
505
|
+
logger.info(`Installed stub OpenGL library to ${stubDestPath}`, "CoreBuilder");
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Copy the core
|
|
509
|
+
copyFileSync(builtPath, destPath);
|
|
510
|
+
|
|
511
|
+
// Make executable
|
|
512
|
+
execSync(`chmod 755 "${destPath}"`);
|
|
513
|
+
|
|
514
|
+
// Fix library paths for mupen64plus to use absolute path to stub library
|
|
515
|
+
if (coreName === "mupen64plus_next") {
|
|
516
|
+
fixLibraryPaths(destPath, coresDir);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
logger.info(`Installed ${coreName} to ${destPath}`, "CoreBuilder");
|
|
520
|
+
|
|
521
|
+
onProgress?.({
|
|
522
|
+
phase: "complete",
|
|
523
|
+
message: "Build complete!",
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
return destPath;
|
|
527
|
+
} catch (error) {
|
|
528
|
+
const errorMessage = getErrorMessage(error);
|
|
529
|
+
logger.error(`Failed to build ${coreName}: ${errorMessage}`, "CoreBuilder");
|
|
530
|
+
|
|
531
|
+
onProgress?.({
|
|
532
|
+
phase: "error",
|
|
533
|
+
message: `Build failed: ${errorMessage}`,
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
throw error;
|
|
537
|
+
} finally {
|
|
538
|
+
// Clean up build directory
|
|
539
|
+
try {
|
|
540
|
+
rmSync(buildDir, { recursive: true, force: true });
|
|
541
|
+
} catch {
|
|
542
|
+
// Ignore cleanup errors
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
/** Percentage multiplier for progress calculations */
|
|
548
|
+
const PERCENT_MULTIPLIER = 100;
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Run a command with progress reporting
|
|
552
|
+
*/
|
|
553
|
+
const runCommand = (
|
|
554
|
+
command: string,
|
|
555
|
+
args: string[],
|
|
556
|
+
options: {
|
|
557
|
+
cwd?: string;
|
|
558
|
+
onProgress?: (progress: BuildProgress) => void;
|
|
559
|
+
phase: BuildPhase;
|
|
560
|
+
/** Total number of compilation targets for progress tracking */
|
|
561
|
+
totalTargets?: number | null;
|
|
562
|
+
}
|
|
563
|
+
): Promise<void> => {
|
|
564
|
+
return new Promise((resolve, reject) => {
|
|
565
|
+
const proc = spawn(command, args, {
|
|
566
|
+
cwd: options.cwd,
|
|
567
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
const outputLines: string[] = [];
|
|
571
|
+
const MAX_OUTPUT_LINES = 50;
|
|
572
|
+
let compiledCount = 0;
|
|
573
|
+
|
|
574
|
+
const handleOutput = (data: Buffer): void => {
|
|
575
|
+
const lines = data.toString().split("\n").filter(Boolean);
|
|
576
|
+
for (const line of lines) {
|
|
577
|
+
outputLines.push(line);
|
|
578
|
+
if (outputLines.length > MAX_OUTPUT_LINES) {
|
|
579
|
+
outputLines.shift();
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Track compilation progress
|
|
583
|
+
if (options.totalTargets && isCompilationLine(line)) {
|
|
584
|
+
compiledCount++;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Report last line as progress
|
|
589
|
+
const lastLine = lines[lines.length - 1];
|
|
590
|
+
if (lastLine && options.onProgress) {
|
|
591
|
+
const progress: BuildProgress = {
|
|
592
|
+
phase: options.phase,
|
|
593
|
+
message: lastLine,
|
|
594
|
+
output: outputLines,
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
// Add progress percentage if we're tracking compilation
|
|
598
|
+
if (options.totalTargets && compiledCount > 0) {
|
|
599
|
+
progress.progressPercent = Math.min(
|
|
600
|
+
Math.round((compiledCount / options.totalTargets) * PERCENT_MULTIPLIER),
|
|
601
|
+
PERCENT_MULTIPLIER
|
|
602
|
+
);
|
|
603
|
+
progress.progressText = `${compiledCount} of ${options.totalTargets} files`;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
options.onProgress(progress);
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
proc.stdout.on("data", handleOutput);
|
|
611
|
+
proc.stderr.on("data", handleOutput);
|
|
612
|
+
|
|
613
|
+
proc.on("close", (code) => {
|
|
614
|
+
if (code === 0) {
|
|
615
|
+
resolve();
|
|
616
|
+
} else {
|
|
617
|
+
const errorOutput = outputLines.slice(-ERROR_OUTPUT_LINES).join("\n");
|
|
618
|
+
reject(new Error(`${command} exited with code ${code}\n${errorOutput}`));
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
proc.on("error", (err) => {
|
|
623
|
+
reject(new Error(`Failed to start ${command}: ${err.message}`));
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Get list of cores that can be built from source
|
|
630
|
+
*/
|
|
631
|
+
export const getBuildableCores = (): Array<{ name: string; description: string }> => {
|
|
632
|
+
if (platform() !== "darwin" || arch() !== "arm64") {
|
|
633
|
+
return [];
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return Object.entries(BUILD_CONFIGS)
|
|
637
|
+
.filter((entry): entry is [string, CoreBuildConfig] => entry[1] !== undefined)
|
|
638
|
+
.map(([name, config]) => ({
|
|
639
|
+
name,
|
|
640
|
+
description: config.description,
|
|
641
|
+
}));
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
export * from "./types";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core builder error types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createTypedError } from '../../utils/typedError';
|
|
6
|
+
|
|
7
|
+
export type CoreBuildErrorCode =
|
|
8
|
+
| 'NO_BUILD_CONFIG'
|
|
9
|
+
| 'MISSING_TOOLS'
|
|
10
|
+
| 'OUTPUT_NOT_FOUND';
|
|
11
|
+
|
|
12
|
+
const { TypedError, isTypedError } = createTypedError<CoreBuildErrorCode>('CoreBuildError');
|
|
13
|
+
export const CoreBuildError = TypedError;
|
|
14
|
+
export type CoreBuildError = InstanceType<typeof TypedError>;
|
|
15
|
+
export const isCoreBuildError = isTypedError;
|