mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-04-03 06:12:07 -07:00
fix: address CodeRabbit follow-ups for PR #40
This commit is contained in:
@@ -427,11 +427,14 @@ function withFindAppBinaryEnvSandbox(run: () => void): void {
|
||||
}
|
||||
}
|
||||
|
||||
function withFindAppBinaryPlatformSandbox(platform: NodeJS.Platform, run: () => void): void {
|
||||
function withFindAppBinaryPlatformSandbox(
|
||||
platform: NodeJS.Platform,
|
||||
run: (pathModule: typeof path) => void,
|
||||
): void {
|
||||
const originalPlatform = process.platform;
|
||||
try {
|
||||
Object.defineProperty(process, 'platform', { value: platform, configurable: true });
|
||||
withFindAppBinaryEnvSandbox(run);
|
||||
withFindAppBinaryEnvSandbox(() => run(platform === 'win32' ? (path.win32 as typeof path) : path));
|
||||
} finally {
|
||||
Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true });
|
||||
}
|
||||
@@ -457,7 +460,7 @@ function withAccessSyncStub(
|
||||
}
|
||||
}
|
||||
|
||||
test('findAppBinary resolves ~/.local/bin/SubMiner.AppImage when it exists', () => {
|
||||
test('findAppBinary resolves ~/.local/bin/SubMiner.AppImage when it exists', { concurrency: false }, () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-test-home-'));
|
||||
const originalHomedir = os.homedir;
|
||||
try {
|
||||
@@ -465,8 +468,8 @@ test('findAppBinary resolves ~/.local/bin/SubMiner.AppImage when it exists', ()
|
||||
const appImage = path.join(baseDir, '.local/bin/SubMiner.AppImage');
|
||||
makeExecutable(appImage);
|
||||
|
||||
withFindAppBinaryPlatformSandbox('linux', () => {
|
||||
const result = findAppBinary('/some/other/path/subminer');
|
||||
withFindAppBinaryPlatformSandbox('linux', (pathModule) => {
|
||||
const result = findAppBinary('/some/other/path/subminer', pathModule);
|
||||
assert.equal(result, appImage);
|
||||
});
|
||||
} finally {
|
||||
@@ -475,16 +478,16 @@ test('findAppBinary resolves ~/.local/bin/SubMiner.AppImage when it exists', ()
|
||||
}
|
||||
});
|
||||
|
||||
test('findAppBinary resolves /opt/SubMiner/SubMiner.AppImage when ~/.local/bin candidate does not exist', () => {
|
||||
test('findAppBinary resolves /opt/SubMiner/SubMiner.AppImage when ~/.local/bin candidate does not exist', { concurrency: false }, () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-test-home-'));
|
||||
const originalHomedir = os.homedir;
|
||||
try {
|
||||
os.homedir = () => baseDir;
|
||||
withFindAppBinaryPlatformSandbox('linux', () => {
|
||||
withFindAppBinaryPlatformSandbox('linux', (pathModule) => {
|
||||
withAccessSyncStub(
|
||||
(filePath) => filePath === '/opt/SubMiner/SubMiner.AppImage',
|
||||
() => {
|
||||
const result = findAppBinary('/some/other/path/subminer');
|
||||
const result = findAppBinary('/some/other/path/subminer', pathModule);
|
||||
assert.equal(result, '/opt/SubMiner/SubMiner.AppImage');
|
||||
},
|
||||
);
|
||||
@@ -495,7 +498,7 @@ test('findAppBinary resolves /opt/SubMiner/SubMiner.AppImage when ~/.local/bin c
|
||||
}
|
||||
});
|
||||
|
||||
test('findAppBinary finds subminer on PATH when AppImage candidates do not exist', () => {
|
||||
test('findAppBinary finds subminer on PATH when AppImage candidates do not exist', { concurrency: false }, () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-test-path-'));
|
||||
const originalHomedir = os.homedir;
|
||||
const originalPath = process.env.PATH;
|
||||
@@ -507,12 +510,12 @@ test('findAppBinary finds subminer on PATH when AppImage candidates do not exist
|
||||
makeExecutable(wrapperPath);
|
||||
process.env.PATH = `${binDir}${path.delimiter}${originalPath ?? ''}`;
|
||||
|
||||
withFindAppBinaryPlatformSandbox('linux', () => {
|
||||
withFindAppBinaryPlatformSandbox('linux', (pathModule) => {
|
||||
withAccessSyncStub(
|
||||
(filePath) => filePath === wrapperPath,
|
||||
() => {
|
||||
// selfPath must differ from wrapperPath so the self-check does not exclude it
|
||||
const result = findAppBinary(path.join(baseDir, 'launcher', 'subminer'));
|
||||
const result = findAppBinary(path.join(baseDir, 'launcher', 'subminer'), pathModule);
|
||||
assert.equal(result, wrapperPath);
|
||||
},
|
||||
);
|
||||
@@ -524,20 +527,27 @@ test('findAppBinary finds subminer on PATH when AppImage candidates do not exist
|
||||
}
|
||||
});
|
||||
|
||||
test('findAppBinary resolves Windows install paths when present', () => {
|
||||
test('findAppBinary resolves Windows install paths when present', { concurrency: false }, () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-test-win-'));
|
||||
const originalHomedir = os.homedir;
|
||||
const originalLocalAppData = process.env.LOCALAPPDATA;
|
||||
try {
|
||||
os.homedir = () => baseDir;
|
||||
process.env.LOCALAPPDATA = path.join(baseDir, 'AppData', 'Local');
|
||||
const appExe = path.join(baseDir, 'AppData', 'Local', 'Programs', 'SubMiner', 'SubMiner.exe');
|
||||
process.env.LOCALAPPDATA = path.win32.join(baseDir, 'AppData', 'Local');
|
||||
const appExe = path.win32.join(
|
||||
baseDir,
|
||||
'AppData',
|
||||
'Local',
|
||||
'Programs',
|
||||
'SubMiner',
|
||||
'SubMiner.exe',
|
||||
);
|
||||
|
||||
withFindAppBinaryPlatformSandbox('win32', () => {
|
||||
withFindAppBinaryPlatformSandbox('win32', (pathModule) => {
|
||||
withAccessSyncStub(
|
||||
(filePath) => filePath === appExe,
|
||||
() => {
|
||||
const result = findAppBinary(path.join(baseDir, 'launcher', 'SubMiner.exe'));
|
||||
const result = findAppBinary(pathModule.join(baseDir, 'launcher', 'SubMiner.exe'), pathModule);
|
||||
assert.equal(result, appExe);
|
||||
},
|
||||
);
|
||||
@@ -553,22 +563,22 @@ test('findAppBinary resolves Windows install paths when present', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('findAppBinary resolves SubMiner.exe on PATH on Windows', () => {
|
||||
test('findAppBinary resolves SubMiner.exe on PATH on Windows', { concurrency: false }, () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-test-win-path-'));
|
||||
const originalHomedir = os.homedir;
|
||||
const originalPath = process.env.PATH;
|
||||
try {
|
||||
os.homedir = () => baseDir;
|
||||
const binDir = path.join(baseDir, 'bin');
|
||||
const wrapperPath = path.join(binDir, 'SubMiner.exe');
|
||||
const binDir = path.win32.join(baseDir, 'bin');
|
||||
const wrapperPath = path.win32.join(binDir, 'SubMiner.exe');
|
||||
makeExecutable(wrapperPath);
|
||||
process.env.PATH = `${binDir}${path.delimiter}${originalPath ?? ''}`;
|
||||
process.env.PATH = `${binDir}${path.win32.delimiter}${originalPath ?? ''}`;
|
||||
|
||||
withFindAppBinaryPlatformSandbox('win32', () => {
|
||||
withFindAppBinaryPlatformSandbox('win32', (pathModule) => {
|
||||
withAccessSyncStub(
|
||||
(filePath) => filePath === wrapperPath,
|
||||
() => {
|
||||
const result = findAppBinary(path.join(baseDir, 'launcher', 'SubMiner.exe'));
|
||||
const result = findAppBinary(pathModule.join(baseDir, 'launcher', 'SubMiner.exe'), pathModule);
|
||||
assert.equal(result, wrapperPath);
|
||||
},
|
||||
);
|
||||
@@ -579,3 +589,35 @@ test('findAppBinary resolves SubMiner.exe on PATH on Windows', () => {
|
||||
fs.rmSync(baseDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('findAppBinary resolves a Windows install directory to SubMiner.exe', { concurrency: false }, () => {
|
||||
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-test-win-dir-'));
|
||||
const originalHomedir = os.homedir;
|
||||
const originalSubminerBinaryPath = process.env.SUBMINER_BINARY_PATH;
|
||||
try {
|
||||
os.homedir = () => baseDir;
|
||||
const installDir = path.win32.join(baseDir, 'Programs', 'SubMiner');
|
||||
const appExe = path.win32.join(installDir, 'SubMiner.exe');
|
||||
process.env.SUBMINER_BINARY_PATH = installDir;
|
||||
fs.mkdirSync(installDir, { recursive: true });
|
||||
fs.writeFileSync(appExe, '#!/bin/sh\nexit 0\n');
|
||||
fs.chmodSync(appExe, 0o755);
|
||||
|
||||
const originalPlatform = process.platform;
|
||||
try {
|
||||
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
|
||||
const result = findAppBinary(path.win32.join(baseDir, 'launcher', 'SubMiner.exe'), path.win32);
|
||||
assert.equal(result, appExe);
|
||||
} finally {
|
||||
Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true });
|
||||
}
|
||||
} finally {
|
||||
os.homedir = originalHomedir;
|
||||
if (originalSubminerBinaryPath === undefined) {
|
||||
delete process.env.SUBMINER_BINARY_PATH;
|
||||
} else {
|
||||
process.env.SUBMINER_BINARY_PATH = originalSubminerBinaryPath;
|
||||
}
|
||||
fs.rmSync(baseDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
isExecutable,
|
||||
resolveBinaryPathCandidate,
|
||||
resolveCommandInvocation,
|
||||
realpathMaybe,
|
||||
isYoutubeTarget,
|
||||
uniqueNormalizedLangCodes,
|
||||
sleep,
|
||||
@@ -35,6 +34,8 @@ type SpawnTarget = {
|
||||
args: string[];
|
||||
};
|
||||
|
||||
type PathModule = Pick<typeof path, 'join' | 'extname' | 'delimiter' | 'sep' | 'resolve'>;
|
||||
|
||||
const DETACHED_IDLE_MPV_PID_FILE = path.join(os.tmpdir(), 'subminer-idle-mpv.pid');
|
||||
const OVERLAY_START_SOCKET_READY_TIMEOUT_MS = 900;
|
||||
const OVERLAY_START_COMMAND_SETTLE_TIMEOUT_MS = 700;
|
||||
@@ -243,29 +244,30 @@ export function detectBackend(backend: Backend): Exclude<Backend, 'auto'> {
|
||||
fail('Could not detect display backend');
|
||||
}
|
||||
|
||||
function resolveAppBinaryCandidate(candidate: string): string {
|
||||
function resolveAppBinaryCandidate(candidate: string, pathModule: PathModule = path): string {
|
||||
const direct = resolveBinaryPathCandidate(candidate);
|
||||
if (!direct) return '';
|
||||
|
||||
if (isExecutable(direct)) {
|
||||
return direct;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
if (fs.existsSync(direct) && fs.statSync(direct).isDirectory()) {
|
||||
for (const candidateBinary of ['SubMiner.exe', 'subminer.exe']) {
|
||||
const nestedCandidate = path.join(direct, candidateBinary);
|
||||
const nestedCandidate = pathModule.join(direct, candidateBinary);
|
||||
if (isExecutable(nestedCandidate)) {
|
||||
return nestedCandidate;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (!path.extname(direct)) {
|
||||
if (isExecutable(direct)) {
|
||||
return direct;
|
||||
}
|
||||
|
||||
if (!pathModule.extname(direct)) {
|
||||
for (const extension of ['.exe', '.cmd', '.bat']) {
|
||||
const withExtension = `${direct}${extension}`;
|
||||
if (isExecutable(withExtension)) {
|
||||
@@ -277,6 +279,10 @@ function resolveAppBinaryCandidate(candidate: string): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (isExecutable(direct)) {
|
||||
return direct;
|
||||
}
|
||||
|
||||
if (process.platform !== 'darwin') {
|
||||
return '';
|
||||
}
|
||||
@@ -291,8 +297,8 @@ function resolveAppBinaryCandidate(candidate: string): string {
|
||||
if (!appPath) return '';
|
||||
|
||||
const candidates = [
|
||||
path.join(appPath, 'Contents', 'MacOS', 'SubMiner'),
|
||||
path.join(appPath, 'Contents', 'MacOS', 'subminer'),
|
||||
pathModule.join(appPath, 'Contents', 'MacOS', 'SubMiner'),
|
||||
pathModule.join(appPath, 'Contents', 'MacOS', 'subminer'),
|
||||
];
|
||||
|
||||
for (const candidateBinary of candidates) {
|
||||
@@ -304,20 +310,20 @@ function resolveAppBinaryCandidate(candidate: string): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
function findCommandOnPath(candidates: string[]): string {
|
||||
const pathDirs = getPathEnv().split(path.delimiter);
|
||||
function findCommandOnPath(candidates: string[], pathModule: PathModule = path): string {
|
||||
const pathDirs = getPathEnv().split(pathModule.delimiter);
|
||||
for (const candidateName of candidates) {
|
||||
for (const dir of pathDirs) {
|
||||
if (!dir) continue;
|
||||
|
||||
const directCandidate = path.join(dir, candidateName);
|
||||
const directCandidate = pathModule.join(dir, candidateName);
|
||||
if (isExecutable(directCandidate)) {
|
||||
return directCandidate;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32' && !path.extname(candidateName)) {
|
||||
if (process.platform === 'win32' && !pathModule.extname(candidateName)) {
|
||||
for (const extension of ['.exe', '.cmd', '.bat']) {
|
||||
const extendedCandidate = path.join(dir, `${candidateName}${extension}`);
|
||||
const extendedCandidate = pathModule.join(dir, `${candidateName}${extension}`);
|
||||
if (isExecutable(extendedCandidate)) {
|
||||
return extendedCandidate;
|
||||
}
|
||||
@@ -329,13 +335,13 @@ function findCommandOnPath(candidates: string[]): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
export function findAppBinary(selfPath: string): string | null {
|
||||
export function findAppBinary(selfPath: string, pathModule: PathModule = path): string | null {
|
||||
const envPaths = [process.env.SUBMINER_APPIMAGE_PATH, process.env.SUBMINER_BINARY_PATH].filter(
|
||||
(candidate): candidate is string => Boolean(candidate),
|
||||
);
|
||||
|
||||
for (const envPath of envPaths) {
|
||||
const resolved = resolveAppBinaryCandidate(envPath);
|
||||
const resolved = resolveAppBinaryCandidate(envPath, pathModule);
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
@@ -345,36 +351,37 @@ export function findAppBinary(selfPath: string): string | null {
|
||||
if (process.platform === 'win32') {
|
||||
const localAppData =
|
||||
process.env.LOCALAPPDATA?.trim() ||
|
||||
(process.env.APPDATA?.trim() || '').replace(/[\\/]Roaming$/i, `${path.sep}Local`) ||
|
||||
path.join(os.homedir(), 'AppData', 'Local');
|
||||
(process.env.APPDATA?.trim() || '').replace(/[\\/]Roaming$/i, `${pathModule.sep}Local`) ||
|
||||
pathModule.join(os.homedir(), 'AppData', 'Local');
|
||||
const programFiles = process.env.ProgramFiles?.trim() || 'C:\\Program Files';
|
||||
const programFilesX86 = process.env['ProgramFiles(x86)']?.trim() || 'C:\\Program Files (x86)';
|
||||
candidates.push(path.join(localAppData, 'Programs', 'SubMiner', 'SubMiner.exe'));
|
||||
candidates.push(path.join(programFiles, 'SubMiner', 'SubMiner.exe'));
|
||||
candidates.push(path.join(programFilesX86, 'SubMiner', 'SubMiner.exe'));
|
||||
candidates.push(pathModule.join(localAppData, 'Programs', 'SubMiner', 'SubMiner.exe'));
|
||||
candidates.push(pathModule.join(programFiles, 'SubMiner', 'SubMiner.exe'));
|
||||
candidates.push(pathModule.join(programFilesX86, 'SubMiner', 'SubMiner.exe'));
|
||||
candidates.push('C:\\SubMiner\\SubMiner.exe');
|
||||
} else if (process.platform === 'darwin') {
|
||||
candidates.push('/Applications/SubMiner.app/Contents/MacOS/SubMiner');
|
||||
candidates.push('/Applications/SubMiner.app/Contents/MacOS/subminer');
|
||||
candidates.push(path.join(os.homedir(), 'Applications/SubMiner.app/Contents/MacOS/SubMiner'));
|
||||
candidates.push(path.join(os.homedir(), 'Applications/SubMiner.app/Contents/MacOS/subminer'));
|
||||
candidates.push(pathModule.join(os.homedir(), 'Applications/SubMiner.app/Contents/MacOS/SubMiner'));
|
||||
candidates.push(pathModule.join(os.homedir(), 'Applications/SubMiner.app/Contents/MacOS/subminer'));
|
||||
} else {
|
||||
candidates.push(path.join(os.homedir(), '.local/bin/SubMiner.AppImage'));
|
||||
candidates.push(pathModule.join(os.homedir(), '.local/bin/SubMiner.AppImage'));
|
||||
candidates.push('/opt/SubMiner/SubMiner.AppImage');
|
||||
}
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const resolved = resolveAppBinaryCandidate(candidate);
|
||||
const resolved = resolveAppBinaryCandidate(candidate, pathModule);
|
||||
if (resolved) return resolved;
|
||||
}
|
||||
|
||||
const fromPath = findCommandOnPath(
|
||||
process.platform === 'win32' ? ['SubMiner', 'subminer'] : ['subminer'],
|
||||
pathModule,
|
||||
);
|
||||
|
||||
if (fromPath) {
|
||||
const resolvedSelf = realpathMaybe(selfPath);
|
||||
const resolvedCandidate = realpathMaybe(fromPath);
|
||||
const resolvedSelf = pathModule.resolve(selfPath);
|
||||
const resolvedCandidate = pathModule.resolve(fromPath);
|
||||
if (resolvedSelf !== resolvedCandidate) return fromPath;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user