fix(linux): auto-install managed plugin copy; include in asset updates (#127)

This commit is contained in:
2026-06-14 17:25:28 -07:00
committed by GitHub
parent ae7e6f82a8
commit a117c5759c
53 changed files with 3050 additions and 152 deletions
+223
View File
@@ -0,0 +1,223 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { log as launcherLog } from './log.js';
import { runAppCommandCaptureOutput, resolveLauncherRuntimePluginPath } from './mpv.js';
import { nowMs } from './time.js';
import { sleep } from './util.js';
import { detectInstalledMpvPlugin } from '../src/main/runtime/first-run-setup-plugin.js';
import {
resolveManagedLinuxRuntimePluginPaths,
type EnsureLinuxRuntimePluginAssetsResult,
} from '../src/main/runtime/linux-runtime-plugin-assets.js';
const RESPONSE_TIMEOUT_MS = 30_000;
type PreflightLog = (
level: 'debug' | 'info' | 'warn' | 'error',
configured: 'debug' | 'info' | 'warn' | 'error',
message: string,
) => void;
type EnsureLinuxRuntimePluginAvailableOptions = {
appPath?: string;
scriptPath?: string;
logLevel?: 'debug' | 'info' | 'warn' | 'error';
platform?: NodeJS.Platform;
homeDir?: string;
xdgConfigHome?: string;
xdgDataHome?: string;
appDataDir?: string;
detectInstalledPlugin?: () => boolean;
resolveRuntimePluginPath?: () => string | null;
isManagedThemeAvailable?: () => boolean;
installManagedPluginAssets?: () => Promise<EnsureLinuxRuntimePluginAssetsResult>;
log?: PreflightLog;
};
type RuntimePluginPreflightResponse = {
ok: boolean;
status: 'installed' | 'already-present' | 'failed';
path?: string;
error?: string;
};
function resolveConfiguredLogLevel(
logLevel: EnsureLinuxRuntimePluginAvailableOptions['logLevel'],
): 'debug' | 'info' | 'warn' | 'error' {
return logLevel ?? 'warn';
}
async function waitForInstallResponse(
responsePath: string,
): Promise<RuntimePluginPreflightResponse | null> {
const deadline = nowMs() + RESPONSE_TIMEOUT_MS;
while (nowMs() < deadline) {
try {
if (fs.existsSync(responsePath)) {
return JSON.parse(fs.readFileSync(responsePath, 'utf8')) as RuntimePluginPreflightResponse;
}
} catch {
// retry until timeout
}
await sleep(100);
}
return null;
}
type InstallManagedPluginAssetsViaAppDeps = {
runAppCommandCaptureOutput?: typeof runAppCommandCaptureOutput;
waitForInstallResponse?: typeof waitForInstallResponse;
};
export async function installManagedPluginAssetsViaApp(
options: {
appPath: string;
logLevel?: 'debug' | 'info' | 'warn' | 'error';
},
deps: InstallManagedPluginAssetsViaAppDeps = {},
): Promise<EnsureLinuxRuntimePluginAssetsResult> {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-runtime-plugin-'));
const responsePath = path.join(tempDir, 'response.json');
const runAppCommand = deps.runAppCommandCaptureOutput ?? runAppCommandCaptureOutput;
const waitForResponse = deps.waitForInstallResponse ?? waitForInstallResponse;
try {
const appArgs = [
'--ensure-linux-runtime-plugin-assets',
'--ensure-linux-runtime-plugin-assets-response-path',
responsePath,
];
const result = runAppCommand(options.appPath, appArgs);
if (result.error) {
return {
ok: false,
status: 'failed',
error: result.error.message,
};
}
if (result.status !== 0) {
const stderr = result.stderr.trim();
const stdout = result.stdout.trim();
return {
ok: false,
status: 'failed',
error:
stderr ||
stdout ||
`Linux runtime plugin asset install command exited with status ${result.status}.`,
};
}
const response = await waitForResponse(responsePath);
if (response) {
return response;
}
const stderr = result.stderr.trim();
const stdout = result.stdout.trim();
return {
ok: false,
status: 'failed',
error:
stderr ||
stdout ||
`Timed out waiting for Linux runtime plugin asset response after app exit status ${result.status}.`,
};
} finally {
try {
fs.rmSync(tempDir, { recursive: true, force: true });
} catch {
// Avoid hiding the install failure or success result behind temp cleanup errors.
}
}
}
export async function ensureLinuxRuntimePluginAvailable(
options: EnsureLinuxRuntimePluginAvailableOptions,
): Promise<void> {
const platform = options.platform ?? process.platform;
if (platform !== 'linux') {
return;
}
const configuredLogLevel = resolveConfiguredLogLevel(options.logLevel);
const log = options.log ?? launcherLog;
const homeDir = options.homeDir ?? os.homedir();
const detectInstalledPlugin =
options.detectInstalledPlugin ??
(() =>
detectInstalledMpvPlugin({
platform,
homeDir,
xdgConfigHome: options.xdgConfigHome ?? process.env.XDG_CONFIG_HOME,
appDataDir: options.appDataDir ?? process.env.APPDATA,
}).installed);
const installedPluginAvailable = detectInstalledPlugin();
const managedPaths = resolveManagedLinuxRuntimePluginPaths({
homeDir,
xdgDataHome: options.xdgDataHome ?? process.env.XDG_DATA_HOME,
});
const resolveRuntimePluginPath =
options.resolveRuntimePluginPath ??
(() => {
if (!options.appPath) return null;
return resolveLauncherRuntimePluginPath({
appPath: options.appPath,
scriptPath: options.scriptPath,
platform,
homeDir,
env: process.env,
});
});
const isManagedThemeAvailable =
options.isManagedThemeAvailable ?? (() => fs.existsSync(managedPaths.themePath));
const runtimePluginAvailable = installedPluginAvailable || Boolean(resolveRuntimePluginPath());
if (runtimePluginAvailable && isManagedThemeAvailable()) {
return;
}
log(
'info',
configuredLogLevel,
'Linux runtime support assets missing; installing managed plugin/theme assets.',
);
const installManagedPluginAssets =
options.installManagedPluginAssets ??
(() => {
if (!options.appPath) {
throw new Error(
'Linux managed runtime plugin assets could not be installed. Launch aborted before starting mpv.',
);
}
return installManagedPluginAssetsViaApp({
appPath: options.appPath,
logLevel: options.logLevel,
});
});
const installResult = await installManagedPluginAssets();
if (!installResult.ok) {
const message = installResult.error || 'Unknown Linux runtime plugin asset install failure.';
log(
'warn',
configuredLogLevel,
`Managed Linux runtime support asset install failed: ${message}`,
);
throw new Error(message);
}
log(
'info',
configuredLogLevel,
`Managed Linux runtime support assets installed: plugin=${installResult.path ?? 'unknown path'} theme=${managedPaths.themePath}`,
);
const runtimePluginPath = resolveRuntimePluginPath();
if (runtimePluginPath) {
return;
}
const message =
`Linux managed runtime plugin assets could not be installed. ` +
`Checked path: ${managedPaths.pluginEntrypointPath}. ` +
'Launch aborted before starting mpv.';
log('warn', configuredLogLevel, message);
throw new Error(message);
}