mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-13 08:12:54 -07:00
feat: inject bundled mpv plugin for managed launches, remove legacy glob (#62)
* feat: inject bundled mpv plugin for managed launches, remove legacy glob - SubMiner-managed launcher and Windows shortcut launches inject the bundled plugin when no global plugin is detected - First-run setup detects and removes legacy global plugin files via OS trash before managed playback starts - Makefile `install-plugin` target and Windows config-rewrite script removed; Linux/macOS install now copies plugin to app data dir - AniList stats search and post-watch tracking now go through the shared rate limiter - Stats cover-art lookup reuses cached AniList data before issuing a new request - Closing mpv in a launcher-managed session now terminates the background Electron app * harden bootstrap version load and clean plugin on uninstall - Use pcall for version.lua in bootstrap.lua so missing version module does not crash plugin startup - Remove plugin/subminer from app-data dirs in uninstall-linux and uninstall-macos targets - Add Lua compat test asserting bootstrap uses defensive pcall for version load - Add release-workflow test asserting uninstall targets clean bundled plugin dirs - Delete completed planning document
This commit is contained in:
@@ -1,101 +0,0 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
function normalizeCandidate(candidate) {
|
||||
if (typeof candidate !== 'string') return '';
|
||||
const trimmed = candidate.trim();
|
||||
return trimmed.length > 0 ? trimmed : '';
|
||||
}
|
||||
|
||||
function fileExists(candidate) {
|
||||
try {
|
||||
return fs.statSync(candidate).isFile();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function unique(values) {
|
||||
return Array.from(new Set(values.filter((value) => value.length > 0)));
|
||||
}
|
||||
|
||||
function findWindowsBinary(repoRoot) {
|
||||
const homeDir = process.env.HOME?.trim() || process.env.USERPROFILE?.trim() || '';
|
||||
const appDataDir = process.env.APPDATA?.trim() || '';
|
||||
const derivedLocalAppData =
|
||||
appDataDir && /[\\/]Roaming$/i.test(appDataDir)
|
||||
? appDataDir.replace(/[\\/]Roaming$/i, `${path.sep}Local`)
|
||||
: '';
|
||||
const localAppData =
|
||||
process.env.LOCALAPPDATA?.trim() ||
|
||||
derivedLocalAppData ||
|
||||
(homeDir ? path.join(homeDir, 'AppData', 'Local') : '');
|
||||
const programFiles = process.env.ProgramFiles?.trim() || 'C:\\Program Files';
|
||||
const programFilesX86 = process.env['ProgramFiles(x86)']?.trim() || 'C:\\Program Files (x86)';
|
||||
|
||||
const candidates = unique([
|
||||
normalizeCandidate(process.env.SUBMINER_BINARY_PATH),
|
||||
normalizeCandidate(process.env.SUBMINER_APPIMAGE_PATH),
|
||||
localAppData ? path.join(localAppData, 'Programs', 'SubMiner', 'SubMiner.exe') : '',
|
||||
path.join(programFiles, 'SubMiner', 'SubMiner.exe'),
|
||||
path.join(programFilesX86, 'SubMiner', 'SubMiner.exe'),
|
||||
'C:\\SubMiner\\SubMiner.exe',
|
||||
path.join(repoRoot, 'release', 'win-unpacked', 'SubMiner.exe'),
|
||||
path.join(repoRoot, 'release', 'SubMiner', 'SubMiner.exe'),
|
||||
path.join(repoRoot, 'release', 'SubMiner.exe'),
|
||||
]);
|
||||
|
||||
return candidates.find((candidate) => fileExists(candidate)) || '';
|
||||
}
|
||||
|
||||
function rewriteBinaryPath(configPath, binaryPath) {
|
||||
const content = fs.readFileSync(configPath, 'utf8');
|
||||
const normalizedPath = binaryPath.replace(/\r?\n/g, ' ').trim();
|
||||
const updated = content.replace(/^binary_path=.*$/m, `binary_path=${normalizedPath}`);
|
||||
if (updated !== content) {
|
||||
fs.writeFileSync(configPath, updated, 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
function rewriteSocketPath(configPath, socketPath) {
|
||||
const content = fs.readFileSync(configPath, 'utf8');
|
||||
const normalizedPath = socketPath.replace(/\r?\n/g, ' ').trim();
|
||||
const updated = content.replace(/^socket_path=.*$/m, `socket_path=${normalizedPath}`);
|
||||
if (updated !== content) {
|
||||
fs.writeFileSync(configPath, updated, 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
const [, , configPathArg, repoRootArg, platformArg] = process.argv;
|
||||
const configPath = normalizeCandidate(configPathArg);
|
||||
const repoRoot = normalizeCandidate(repoRootArg) || process.cwd();
|
||||
const platform = normalizeCandidate(platformArg) || process.platform;
|
||||
|
||||
if (!configPath) {
|
||||
console.error('[ERROR] Missing plugin config path');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!fileExists(configPath)) {
|
||||
console.error(`[ERROR] Plugin config not found: ${configPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (platform !== 'win32') {
|
||||
console.log('[INFO] Skipping binary_path rewrite for non-Windows platform');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const windowsSocketPath = '\\\\.\\pipe\\subminer-socket';
|
||||
rewriteSocketPath(configPath, windowsSocketPath);
|
||||
|
||||
const binaryPath = findWindowsBinary(repoRoot);
|
||||
if (!binaryPath) {
|
||||
console.warn(
|
||||
`[WARN] Configured plugin socket_path=${windowsSocketPath} but could not detect SubMiner.exe; set binary_path manually or provide SUBMINER_BINARY_PATH`,
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
rewriteBinaryPath(configPath, binaryPath);
|
||||
console.log(`[INFO] Configured plugin socket_path=${windowsSocketPath} binary_path=${binaryPath}`);
|
||||
@@ -1,6 +1,8 @@
|
||||
local MODULE_PATHS = {
|
||||
"plugin/subminer/bootstrap.lua",
|
||||
"plugin/subminer/hover.lua",
|
||||
"plugin/subminer/environment.lua",
|
||||
"plugin/subminer/version.lua",
|
||||
}
|
||||
|
||||
local LEGACY_PARSER_CANDIDATES = {
|
||||
@@ -48,6 +50,12 @@ local function assert_loadfile_ok(path)
|
||||
assert_true(chunk ~= nil, "loadfile failed for " .. path .. ": " .. tostring(err))
|
||||
end
|
||||
|
||||
local function assert_bootstrap_uses_defensive_version_load()
|
||||
local source = read_file("plugin/subminer/bootstrap.lua")
|
||||
assert_true(not source:find('require%("version"%)'), "bootstrap.lua must not hard-require version.lua")
|
||||
assert_true(source:find('pcall%(require, "version"%)') ~= nil, "bootstrap.lua must load version.lua with pcall")
|
||||
end
|
||||
|
||||
local function normalize_execute_result(ok, why, code)
|
||||
if type(ok) == "number" then
|
||||
return ok == 0, ok
|
||||
@@ -128,6 +136,7 @@ for _, path in ipairs(MODULE_PATHS) do
|
||||
assert_no_legacy_incompatible_continue(path)
|
||||
assert_loadfile_ok(path)
|
||||
end
|
||||
assert_bootstrap_uses_defensive_version_load()
|
||||
|
||||
local parser = find_legacy_parser()
|
||||
if parser then
|
||||
|
||||
Reference in New Issue
Block a user