mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-27 16:49:51 -07:00
Fix Windows mpv logging and add log export (#88)
This commit is contained in:
@@ -163,7 +163,7 @@ test('buildSubminerScriptOpts includes aniskip payload fields', () => {
|
||||
const payloadMatch = opts.match(/subminer-aniskip_payload=([^,]+)/);
|
||||
assert.match(opts, /subminer-binary_path=\/tmp\/SubMiner\.AppImage/);
|
||||
assert.match(opts, /subminer-socket_path=\/tmp\/subminer\.sock/);
|
||||
assert.match(opts, /subminer-log_level=debug/);
|
||||
assert.doesNotMatch(opts, /subminer-log_level=/);
|
||||
assert.match(opts, /subminer-aniskip_title=Frieren: Beyond Journey's End/);
|
||||
assert.match(opts, /subminer-aniskip_season=1/);
|
||||
assert.match(opts, /subminer-aniskip_episode=5/);
|
||||
|
||||
@@ -564,7 +564,7 @@ export function buildSubminerScriptOpts(
|
||||
appPath: string,
|
||||
socketPath: string,
|
||||
aniSkipMetadata: AniSkipMetadata | null,
|
||||
logLevel: LogLevel = 'info',
|
||||
_logLevel: LogLevel = 'info',
|
||||
extraParts: string[] = [],
|
||||
): string {
|
||||
const hasBinaryPath = extraParts.some((part) => part.startsWith('subminer-binary_path='));
|
||||
@@ -574,9 +574,6 @@ export function buildSubminerScriptOpts(
|
||||
...(hasSocketPath ? [] : [`subminer-socket_path=${sanitizeScriptOptValue(socketPath)}`]),
|
||||
...extraParts.map(sanitizeScriptOptValue),
|
||||
];
|
||||
if (logLevel !== 'info') {
|
||||
parts.push(`subminer-log_level=${sanitizeScriptOptValue(logLevel)}`);
|
||||
}
|
||||
if (aniSkipMetadata && aniSkipMetadata.title) {
|
||||
parts.push(`subminer-aniskip_title=${sanitizeScriptOptValue(aniSkipMetadata.title)}`);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { LauncherCommandContext } from './context.js';
|
||||
import { runConfigCommand } from './config-command.js';
|
||||
import { runDictionaryCommand } from './dictionary-command.js';
|
||||
import { runDoctorCommand } from './doctor-command.js';
|
||||
import { runLogsCommand } from './logs-command.js';
|
||||
import { runMpvPreAppCommand } from './mpv-command.js';
|
||||
import { runAppPassthroughCommand } from './app-command.js';
|
||||
import { runStatsCommand } from './stats-command.js';
|
||||
@@ -169,6 +170,33 @@ test('doctor command forwards refresh-known-words to app binary', () => {
|
||||
assert.deepEqual(forwarded, [['--refresh-known-words']]);
|
||||
});
|
||||
|
||||
test('logs command exports logs and writes archive path', () => {
|
||||
const writes: string[] = [];
|
||||
const context = createContext();
|
||||
context.args.logsExport = true;
|
||||
context.processAdapter = {
|
||||
...context.processAdapter,
|
||||
writeStdout: (text) => writes.push(text),
|
||||
};
|
||||
|
||||
const handled = runLogsCommand(context, {
|
||||
exportLogsArchive: () => ({
|
||||
zipPath: '/tmp/subminer-logs.zip',
|
||||
exportedFiles: ['/tmp/app.log'],
|
||||
mode: 'current-day',
|
||||
}),
|
||||
});
|
||||
|
||||
assert.equal(handled, true);
|
||||
assert.deepEqual(writes, ['/tmp/subminer-logs.zip\n']);
|
||||
});
|
||||
|
||||
test('logs command ignores unrelated launcher commands', () => {
|
||||
const context = createContext();
|
||||
|
||||
assert.equal(runLogsCommand(context), false);
|
||||
});
|
||||
|
||||
test('app command starts default macOS background app detached from launcher', () => {
|
||||
const context = createContext();
|
||||
context.args.appPassthrough = true;
|
||||
@@ -185,7 +213,7 @@ test('app command starts default macOS background app detached from launcher', (
|
||||
});
|
||||
|
||||
assert.equal(handled, true);
|
||||
assert.deepEqual(calls, ['detached:/tmp/subminer.app:info']);
|
||||
assert.deepEqual(calls, ['detached:/tmp/subminer.app:warn']);
|
||||
});
|
||||
|
||||
test('app command starts default Linux background app detached from launcher', () => {
|
||||
@@ -204,7 +232,7 @@ test('app command starts default Linux background app detached from launcher', (
|
||||
});
|
||||
|
||||
assert.equal(handled, true);
|
||||
assert.deepEqual(calls, ['detached:/tmp/subminer.app:info']);
|
||||
assert.deepEqual(calls, ['detached:/tmp/subminer.app:warn']);
|
||||
});
|
||||
|
||||
test('app command keeps explicit passthrough args attached', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { runAppCommandWithInherit } from '../mpv.js';
|
||||
import { shouldForwardLogLevel } from '../types.js';
|
||||
import type { LauncherCommandContext } from './context.js';
|
||||
|
||||
interface DictionaryCommandDeps {
|
||||
@@ -35,7 +36,7 @@ export function runDictionaryCommand(
|
||||
if (typeof args.dictionaryTarget === 'string' && args.dictionaryTarget.trim()) {
|
||||
forwarded.push('--dictionary-target', args.dictionaryTarget);
|
||||
}
|
||||
if (args.logLevel !== 'info') {
|
||||
if (shouldForwardLogLevel(args.logLevel)) {
|
||||
forwarded.push('--log-level', args.logLevel);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { fail } from '../log.js';
|
||||
import { runAppCommandWithInherit } from '../mpv.js';
|
||||
import { commandExists } from '../util.js';
|
||||
import { runJellyfinPlayMenu } from '../jellyfin.js';
|
||||
import { shouldForwardLogLevel } from '../types.js';
|
||||
import type { LauncherCommandContext } from './context.js';
|
||||
|
||||
export async function runJellyfinCommand(context: LauncherCommandContext): Promise<boolean> {
|
||||
@@ -18,7 +19,7 @@ export async function runJellyfinCommand(context: LauncherCommandContext): Promi
|
||||
|
||||
if (args.jellyfin) {
|
||||
const forwarded = ['--jellyfin'];
|
||||
if (args.logLevel !== 'info') forwarded.push('--log-level', args.logLevel);
|
||||
if (shouldForwardLogLevel(args.logLevel)) forwarded.push('--log-level', args.logLevel);
|
||||
appendPasswordStore(forwarded);
|
||||
runAppCommandWithInherit(appPath, forwarded);
|
||||
return true;
|
||||
@@ -42,7 +43,7 @@ export async function runJellyfinCommand(context: LauncherCommandContext): Promi
|
||||
'--jellyfin-password',
|
||||
password,
|
||||
];
|
||||
if (args.logLevel !== 'info') forwarded.push('--log-level', args.logLevel);
|
||||
if (shouldForwardLogLevel(args.logLevel)) forwarded.push('--log-level', args.logLevel);
|
||||
appendPasswordStore(forwarded);
|
||||
runAppCommandWithInherit(appPath, forwarded);
|
||||
return true;
|
||||
@@ -50,7 +51,7 @@ export async function runJellyfinCommand(context: LauncherCommandContext): Promi
|
||||
|
||||
if (args.jellyfinLogout) {
|
||||
const forwarded = ['--jellyfin-logout'];
|
||||
if (args.logLevel !== 'info') forwarded.push('--log-level', args.logLevel);
|
||||
if (shouldForwardLogLevel(args.logLevel)) forwarded.push('--log-level', args.logLevel);
|
||||
appendPasswordStore(forwarded);
|
||||
runAppCommandWithInherit(appPath, forwarded);
|
||||
return true;
|
||||
@@ -69,7 +70,7 @@ export async function runJellyfinCommand(context: LauncherCommandContext): Promi
|
||||
|
||||
if (args.jellyfinDiscovery) {
|
||||
const forwarded = ['--background', '--jellyfin-remote-announce'];
|
||||
if (args.logLevel !== 'info') forwarded.push('--log-level', args.logLevel);
|
||||
if (shouldForwardLogLevel(args.logLevel)) forwarded.push('--log-level', args.logLevel);
|
||||
appendPasswordStore(forwarded);
|
||||
runAppCommandWithInherit(appPath, forwarded);
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { exportLogsArchiveForCurrentUser } from '../../src/main/runtime/log-export.js';
|
||||
import type { ExportLogsResult } from '../../src/main/runtime/log-export.js';
|
||||
import type { LauncherCommandContext } from './context.js';
|
||||
|
||||
interface LogsCommandDeps {
|
||||
exportLogsArchive(): ExportLogsResult;
|
||||
}
|
||||
|
||||
const defaultDeps: LogsCommandDeps = {
|
||||
exportLogsArchive: () => exportLogsArchiveForCurrentUser(),
|
||||
};
|
||||
|
||||
export function runLogsCommand(
|
||||
context: LauncherCommandContext,
|
||||
deps: LogsCommandDeps = defaultDeps,
|
||||
): boolean {
|
||||
if (!context.args.logsExport) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = deps.exportLogsArchive();
|
||||
context.processAdapter.writeStdout(`${result.zipPath}\n`);
|
||||
return true;
|
||||
}
|
||||
@@ -36,6 +36,7 @@ function createContext(): LauncherCommandContext {
|
||||
texthookerOpenBrowser: false,
|
||||
useRofi: false,
|
||||
logLevel: 'info',
|
||||
logRotation: 7,
|
||||
passwordStore: '',
|
||||
target: 'https://www.youtube.com/watch?v=65Ovd7t8sNw',
|
||||
targetKind: 'url',
|
||||
@@ -55,6 +56,7 @@ function createContext(): LauncherCommandContext {
|
||||
stats: false,
|
||||
doctor: false,
|
||||
doctorRefreshKnownWords: false,
|
||||
logsExport: false,
|
||||
version: false,
|
||||
settings: false,
|
||||
configPath: false,
|
||||
@@ -321,6 +323,7 @@ test('plugin auto-start playback attaches a warm background app through the laun
|
||||
test('plugin auto-start attach mode reuses launcher-resolved config dir for app control', async () => {
|
||||
const context = createContext();
|
||||
const originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
|
||||
const originalAppData = process.env.APPDATA;
|
||||
const xdgConfigHome = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-test-xdg-'));
|
||||
const expectedConfigDir = path.join(xdgConfigHome, 'SubMiner');
|
||||
fs.mkdirSync(expectedConfigDir, { recursive: true });
|
||||
@@ -347,6 +350,7 @@ test('plugin auto-start attach mode reuses launcher-resolved config dir for app
|
||||
|
||||
try {
|
||||
process.env.XDG_CONFIG_HOME = xdgConfigHome;
|
||||
process.env.APPDATA = xdgConfigHome;
|
||||
|
||||
await runPlaybackCommandWithDeps(context, {
|
||||
ensurePlaybackSetupReady: async () => {},
|
||||
@@ -376,6 +380,11 @@ test('plugin auto-start attach mode reuses launcher-resolved config dir for app
|
||||
} else {
|
||||
process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
|
||||
}
|
||||
if (originalAppData === undefined) {
|
||||
delete process.env.APPDATA;
|
||||
} else {
|
||||
process.env.APPDATA = originalAppData;
|
||||
}
|
||||
fs.rmSync(xdgConfigHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from 'node:path';
|
||||
import { runAppCommandAttached } from '../mpv.js';
|
||||
import { nowMs } from '../time.js';
|
||||
import { sleep } from '../util.js';
|
||||
import { shouldForwardLogLevel } from '../types.js';
|
||||
import type { LauncherCommandContext } from './context.js';
|
||||
|
||||
type StatsCommandResponse = {
|
||||
@@ -156,7 +157,7 @@ export async function runStatsCommand(
|
||||
if (args.statsCleanupLifetime) {
|
||||
forwarded.push('--stats-cleanup-lifetime');
|
||||
}
|
||||
if (args.logLevel !== 'info') {
|
||||
if (shouldForwardLogLevel(args.logLevel)) {
|
||||
forwarded.push('--log-level', args.logLevel);
|
||||
}
|
||||
const attachedExitPromise = resolvedDeps.runAppCommandAttached(
|
||||
|
||||
@@ -13,6 +13,7 @@ test('launcher root help lists subcommands', () => {
|
||||
assert.match(output, /doctor/);
|
||||
assert.match(output, /config/);
|
||||
assert.match(output, /mpv/);
|
||||
assert.match(output, /logs/);
|
||||
assert.match(output, /dictionary\|dict/);
|
||||
assert.match(output, /texthooker/);
|
||||
assert.match(output, /app\|bin/);
|
||||
|
||||
+50
-1
@@ -1,12 +1,14 @@
|
||||
import { fail } from './log.js';
|
||||
import type {
|
||||
Args,
|
||||
LauncherLoggingConfig,
|
||||
LauncherJellyfinConfig,
|
||||
LauncherMpvConfig,
|
||||
LauncherYoutubeSubgenConfig,
|
||||
LogLevel,
|
||||
PluginRuntimeConfig,
|
||||
} from './types.js';
|
||||
import { normalizeLogRotation } from '../src/shared/log-files.js';
|
||||
import {
|
||||
applyInvocationsToArgs,
|
||||
applyRootOptionsToArgs,
|
||||
@@ -52,6 +54,52 @@ export function loadLauncherMpvConfig(): LauncherMpvConfig {
|
||||
return parseLauncherMpvConfig(root);
|
||||
}
|
||||
|
||||
function parseLogLevelConfig(value: unknown): LogLevel | undefined {
|
||||
if (typeof value !== 'string') return undefined;
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (
|
||||
normalized === 'debug' ||
|
||||
normalized === 'info' ||
|
||||
normalized === 'warn' ||
|
||||
normalized === 'error'
|
||||
) {
|
||||
return normalized;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function parseLogRotationConfig(value: unknown): LauncherLoggingConfig['rotation'] {
|
||||
return normalizeLogRotation(value);
|
||||
}
|
||||
|
||||
function parseLogFileConfig(value: unknown): boolean | undefined {
|
||||
return typeof value === 'boolean' ? value : undefined;
|
||||
}
|
||||
|
||||
export function loadLauncherLoggingConfig(): LauncherLoggingConfig {
|
||||
const root = readLauncherMainConfigObject();
|
||||
if (!root) return {};
|
||||
const logging =
|
||||
root.logging && typeof root.logging === 'object' && !Array.isArray(root.logging)
|
||||
? (root.logging as Record<string, unknown>)
|
||||
: null;
|
||||
const files =
|
||||
logging?.files && typeof logging.files === 'object' && !Array.isArray(logging.files)
|
||||
? (logging.files as Record<string, unknown>)
|
||||
: null;
|
||||
return {
|
||||
level: parseLogLevelConfig(logging?.level),
|
||||
rotation: parseLogRotationConfig(logging?.rotation),
|
||||
files: files
|
||||
? {
|
||||
app: parseLogFileConfig(files.app),
|
||||
launcher: parseLogFileConfig(files.launcher),
|
||||
mpv: parseLogFileConfig(files.mpv),
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function hasLauncherExternalYomitanProfileConfig(): boolean {
|
||||
return readExternalYomitanProfilePath(readLauncherMainConfigObject()) !== null;
|
||||
}
|
||||
@@ -65,9 +113,10 @@ export function parseArgs(
|
||||
scriptName: string,
|
||||
launcherConfig: LauncherYoutubeSubgenConfig,
|
||||
launcherMpvConfig: LauncherMpvConfig = {},
|
||||
launcherLoggingConfig: LauncherLoggingConfig = {},
|
||||
): Args {
|
||||
const topLevelCommand = resolveTopLevelCommand(argv);
|
||||
const parsed = createDefaultArgs(launcherConfig, launcherMpvConfig);
|
||||
const parsed = createDefaultArgs(launcherConfig, launcherMpvConfig, launcherLoggingConfig);
|
||||
|
||||
if (topLevelCommand && (topLevelCommand.name === 'app' || topLevelCommand.name === 'bin')) {
|
||||
parsed.appPassthrough = true;
|
||||
|
||||
@@ -51,6 +51,13 @@ test('createDefaultArgs seeds mpv profile from launcher config', () => {
|
||||
assert.equal(parsed.profile, 'anime');
|
||||
});
|
||||
|
||||
test('createDefaultArgs seeds log level from launcher logging config', () => {
|
||||
const parsed = createDefaultArgs({}, {}, { level: 'debug', rotation: 14 });
|
||||
|
||||
assert.equal(parsed.logLevel, 'debug');
|
||||
assert.equal(parsed.logRotation, 14);
|
||||
});
|
||||
|
||||
test('applyRootOptionsToArgs appends CLI mpv profile to configured profile', () => {
|
||||
const parsed = createDefaultArgs({}, { profile: 'anime' });
|
||||
|
||||
@@ -131,6 +138,8 @@ test('applyInvocationsToArgs maps config and jellyfin invocation state', () => {
|
||||
doctorTriggered: false,
|
||||
doctorLogLevel: null,
|
||||
doctorRefreshKnownWords: false,
|
||||
logsTriggered: false,
|
||||
logsExport: false,
|
||||
texthookerTriggered: false,
|
||||
texthookerLogLevel: null,
|
||||
texthookerOpenBrowser: false,
|
||||
@@ -175,6 +184,8 @@ test('applyInvocationsToArgs maps settings invocation to settings window', () =>
|
||||
doctorTriggered: false,
|
||||
doctorLogLevel: null,
|
||||
doctorRefreshKnownWords: false,
|
||||
logsTriggered: false,
|
||||
logsExport: false,
|
||||
texthookerTriggered: false,
|
||||
texthookerLogLevel: null,
|
||||
texthookerOpenBrowser: false,
|
||||
@@ -212,6 +223,8 @@ test('applyInvocationsToArgs fails when config invocation has no action', () =>
|
||||
doctorTriggered: false,
|
||||
doctorLogLevel: null,
|
||||
doctorRefreshKnownWords: false,
|
||||
logsTriggered: false,
|
||||
logsExport: false,
|
||||
texthookerTriggered: false,
|
||||
texthookerLogLevel: null,
|
||||
texthookerOpenBrowser: false,
|
||||
@@ -247,6 +260,8 @@ test('applyInvocationsToArgs maps texthooker browser-open request', () => {
|
||||
doctorTriggered: false,
|
||||
doctorLogLevel: null,
|
||||
doctorRefreshKnownWords: false,
|
||||
logsTriggered: false,
|
||||
logsExport: false,
|
||||
texthookerTriggered: true,
|
||||
texthookerLogLevel: null,
|
||||
texthookerOpenBrowser: true,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { fail } from '../log.js';
|
||||
import type {
|
||||
Args,
|
||||
Backend,
|
||||
LauncherLoggingConfig,
|
||||
LauncherMpvConfig,
|
||||
LauncherYoutubeSubgenConfig,
|
||||
LogLevel,
|
||||
@@ -106,6 +107,7 @@ function parseDictionaryAnilistId(value: string): number {
|
||||
export function createDefaultArgs(
|
||||
launcherConfig: LauncherYoutubeSubgenConfig,
|
||||
mpvConfig: LauncherMpvConfig = {},
|
||||
loggingConfig: LauncherLoggingConfig = {},
|
||||
): Args {
|
||||
const configuredSecondaryLangs = uniqueNormalizedLangCodes(
|
||||
launcherConfig.secondarySubLanguages ?? [],
|
||||
@@ -162,6 +164,7 @@ export function createDefaultArgs(
|
||||
statsCleanupLifetime: false,
|
||||
doctor: false,
|
||||
doctorRefreshKnownWords: false,
|
||||
logsExport: false,
|
||||
version: false,
|
||||
update: false,
|
||||
settings: false,
|
||||
@@ -195,7 +198,8 @@ export function createDefaultArgs(
|
||||
texthookerOnly: false,
|
||||
texthookerOpenBrowser: false,
|
||||
useRofi: false,
|
||||
logLevel: 'info',
|
||||
logLevel: loggingConfig.level ?? 'warn',
|
||||
logRotation: loggingConfig.rotation ?? 7,
|
||||
passwordStore: '',
|
||||
target: '',
|
||||
targetKind: '',
|
||||
@@ -260,6 +264,10 @@ export function applyInvocationsToArgs(parsed: Args, invocations: CliInvocations
|
||||
}
|
||||
if (invocations.doctorTriggered) parsed.doctor = true;
|
||||
if (invocations.doctorRefreshKnownWords) parsed.doctorRefreshKnownWords = true;
|
||||
if (invocations.logsTriggered && !invocations.logsExport) {
|
||||
fail('Logs command requires -e or --export.');
|
||||
}
|
||||
if (invocations.logsExport) parsed.logsExport = true;
|
||||
if (invocations.texthookerTriggered) parsed.texthookerOnly = true;
|
||||
if (invocations.texthookerOpenBrowser) parsed.texthookerOpenBrowser = true;
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ export interface CliInvocations {
|
||||
doctorTriggered: boolean;
|
||||
doctorLogLevel: string | null;
|
||||
doctorRefreshKnownWords: boolean;
|
||||
logsTriggered: boolean;
|
||||
logsExport: boolean;
|
||||
texthookerTriggered: boolean;
|
||||
texthookerLogLevel: string | null;
|
||||
texthookerOpenBrowser: boolean;
|
||||
@@ -91,6 +93,7 @@ function getTopLevelCommand(argv: string[]): { name: string; index: number } | n
|
||||
'config',
|
||||
'settings',
|
||||
'mpv',
|
||||
'logs',
|
||||
'dictionary',
|
||||
'dict',
|
||||
'stats',
|
||||
@@ -158,6 +161,8 @@ export function parseCliPrograms(
|
||||
let statsLogLevel: string | null = null;
|
||||
let doctorLogLevel: string | null = null;
|
||||
let doctorRefreshKnownWords = false;
|
||||
let logsTriggered = false;
|
||||
let logsExport = false;
|
||||
let texthookerLogLevel: string | null = null;
|
||||
let texthookerOpenBrowser = false;
|
||||
let doctorTriggered = false;
|
||||
@@ -294,6 +299,15 @@ export function parseCliPrograms(
|
||||
doctorRefreshKnownWords = options.refreshKnownWords === true;
|
||||
});
|
||||
|
||||
commandProgram
|
||||
.command('logs')
|
||||
.description('Log file helpers')
|
||||
.option('-e, --export', 'Export sanitized log archive')
|
||||
.action((options: Record<string, unknown>) => {
|
||||
logsTriggered = true;
|
||||
logsExport = options.export === true;
|
||||
});
|
||||
|
||||
commandProgram
|
||||
.command('config')
|
||||
.description('Config file helpers (path|show)')
|
||||
@@ -388,6 +402,8 @@ export function parseCliPrograms(
|
||||
doctorTriggered,
|
||||
doctorLogLevel,
|
||||
doctorRefreshKnownWords,
|
||||
logsTriggered,
|
||||
logsExport,
|
||||
texthookerTriggered,
|
||||
texthookerLogLevel,
|
||||
texthookerOpenBrowser,
|
||||
|
||||
@@ -40,6 +40,7 @@ function validBackendOrDefault(value: unknown, fallback: Backend): Backend {
|
||||
|
||||
export function parsePluginRuntimeConfigFromMainConfig(
|
||||
root: Record<string, unknown> | null,
|
||||
logLevel: LogLevel = 'info',
|
||||
): PluginRuntimeConfig {
|
||||
const mpvConfig = root ? parseLauncherMpvConfig(root) : {};
|
||||
const texthooker = rootObject(root, 'texthooker');
|
||||
@@ -48,6 +49,7 @@ export function parsePluginRuntimeConfigFromMainConfig(
|
||||
socketPath: mpvConfig.socketPath ?? DEFAULT_SOCKET_PATH,
|
||||
binaryPath: mpvConfig.subminerBinaryPath ?? '',
|
||||
backend: validBackendOrDefault(mpvConfig.backend, 'auto'),
|
||||
logLevel,
|
||||
autoStart: booleanOrDefault(mpvConfig.autoStartSubMiner, true),
|
||||
autoStartVisibleOverlay: booleanOrDefault(root?.auto_start_overlay, false),
|
||||
autoStartPauseUntilReady: booleanOrDefault(mpvConfig.pauseUntilOverlayReady, true),
|
||||
@@ -65,7 +67,7 @@ export function buildPluginRuntimeScriptOptParts(
|
||||
}
|
||||
|
||||
export function readPluginRuntimeConfig(logLevel: LogLevel): PluginRuntimeConfig {
|
||||
const parsed = parsePluginRuntimeConfigFromMainConfig(readLauncherMainConfigObject());
|
||||
const parsed = parsePluginRuntimeConfigFromMainConfig(readLauncherMainConfigObject(), logLevel);
|
||||
|
||||
log(
|
||||
'debug',
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
JellyfinItemEntry,
|
||||
JellyfinGroupEntry,
|
||||
} from './types.js';
|
||||
import { shouldForwardLogLevel } from './types.js';
|
||||
import { log, fail, getMpvLogPath } from './log.js';
|
||||
import { nowMs } from './time.js';
|
||||
import { commandExists, resolvePathMaybe, sleep } from './util.js';
|
||||
@@ -1036,7 +1037,7 @@ export async function runJellyfinPlayMenu(
|
||||
fail(`MPV IPC socket not ready: ${mpvSocketPath}`);
|
||||
}
|
||||
const forwarded = ['--start', '--jellyfin-play', `--jellyfin-item-id=${itemId}`];
|
||||
if (args.logLevel !== 'info') forwarded.push('--log-level', args.logLevel);
|
||||
if (shouldForwardLogLevel(args.logLevel)) forwarded.push('--log-level', args.logLevel);
|
||||
if (args.passwordStore) forwarded.push('--password-store', args.passwordStore);
|
||||
runAppCommandWithInheritLogged(appPath, forwarded, args.logLevel, 'jellyfin-play');
|
||||
}
|
||||
|
||||
+24
-5
@@ -1,6 +1,14 @@
|
||||
import type { LogLevel } from './types.js';
|
||||
import { DEFAULT_MPV_LOG_FILE, getDefaultLauncherLogFile } from './types.js';
|
||||
import { appendLogLine, resolveDefaultLogFilePath } from '../src/shared/log-files.js';
|
||||
import { getDefaultLauncherLogFile } from './types.js';
|
||||
import {
|
||||
appendLogLine,
|
||||
DEFAULT_LOG_ROTATION,
|
||||
isLogFileEnabled,
|
||||
normalizeLogRotation,
|
||||
pruneLogDirectoryForPath,
|
||||
resolveDefaultLogFilePath,
|
||||
type LogRotation,
|
||||
} from '../src/shared/log-files.js';
|
||||
|
||||
export const COLORS = {
|
||||
red: '\x1b[0;31m',
|
||||
@@ -22,25 +30,36 @@ export function shouldLog(level: LogLevel, configured: LogLevel): boolean {
|
||||
}
|
||||
|
||||
export function getMpvLogPath(): string {
|
||||
if (!isLogFileEnabled('mpv')) return '';
|
||||
const envPath = process.env.SUBMINER_MPV_LOG?.trim();
|
||||
if (envPath) return envPath;
|
||||
return DEFAULT_MPV_LOG_FILE;
|
||||
const logPath = envPath || resolveDefaultLogFilePath('mpv');
|
||||
pruneLogDirectoryForPath(logPath, getLogRotation());
|
||||
return logPath;
|
||||
}
|
||||
|
||||
export function getLauncherLogPath(): string {
|
||||
if (!isLogFileEnabled('launcher')) return '';
|
||||
const envPath = process.env.SUBMINER_LAUNCHER_LOG?.trim();
|
||||
if (envPath) return envPath;
|
||||
return getDefaultLauncherLogFile();
|
||||
}
|
||||
|
||||
export function getAppLogPath(): string {
|
||||
if (!isLogFileEnabled('app')) return '';
|
||||
const envPath = process.env.SUBMINER_APP_LOG?.trim();
|
||||
if (envPath) return envPath;
|
||||
return resolveDefaultLogFilePath('app');
|
||||
}
|
||||
|
||||
function getLogRotation(): LogRotation {
|
||||
return normalizeLogRotation(process.env.SUBMINER_LOG_ROTATION) ?? DEFAULT_LOG_ROTATION;
|
||||
}
|
||||
|
||||
function appendTimestampedLog(logPath: string, message: string): void {
|
||||
appendLogLine(logPath, `[${new Date().toISOString()}] ${message}`);
|
||||
if (!logPath.trim()) return;
|
||||
appendLogLine(logPath, `[${new Date().toISOString()}] ${message}`, {
|
||||
rotation: getLogRotation(),
|
||||
});
|
||||
}
|
||||
|
||||
export function appendToMpvLog(message: string): void {
|
||||
|
||||
+32
-2
@@ -124,6 +124,29 @@ test('short version flag prints installed app version without requiring app bina
|
||||
});
|
||||
});
|
||||
|
||||
test('logs export writes sanitized archive without requiring app binary', () => {
|
||||
withTempDir((root) => {
|
||||
const homeDir = path.join(root, 'home');
|
||||
const xdgConfigHome = path.join(root, 'xdg');
|
||||
const logsDir =
|
||||
process.platform === 'win32'
|
||||
? path.join(xdgConfigHome, 'SubMiner', 'logs')
|
||||
: path.join(homeDir, '.config', 'SubMiner', 'logs');
|
||||
fs.mkdirSync(logsDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(logsDir, 'app-2026-W21.log'), `/home/kyle/video.mkv\n`, 'utf8');
|
||||
|
||||
const result = runLauncher(['logs', '-e'], makeTestEnv(homeDir, xdgConfigHome));
|
||||
|
||||
assert.equal(result.status, 0, `stdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
|
||||
const zipPath = result.stdout.trim();
|
||||
assert.match(zipPath, /subminer-logs-.+\.zip$/);
|
||||
assert.equal(fs.existsSync(zipPath), true);
|
||||
const archive = fs.readFileSync(zipPath);
|
||||
assert.equal(archive.includes(Buffer.from('/home/kyle')), false);
|
||||
assert.equal(archive.includes(Buffer.from('/home/<user>')), true);
|
||||
});
|
||||
});
|
||||
|
||||
test('config path prefers jsonc over json for same directory', () => {
|
||||
withTempDir((root) => {
|
||||
const homeDir = path.join(root, 'home');
|
||||
@@ -395,7 +418,7 @@ ${bunBinary} -e "const net=require('node:net'); const fs=require('node:fs'); con
|
||||
});
|
||||
});
|
||||
|
||||
test('launcher forwards non-info log level into mpv plugin script opts', { timeout: 15000 }, () => {
|
||||
test('launcher forwards non-info log level into mpv logging args', { timeout: 15000 }, () => {
|
||||
withTempDir((root) => {
|
||||
const homeDir = path.join(root, 'home');
|
||||
const xdgConfigHome = path.join(root, 'xdg');
|
||||
@@ -430,6 +453,11 @@ test('launcher forwards non-info log level into mpv plugin script opts', { timeo
|
||||
autoStartSubMiner: true,
|
||||
pauseUntilOverlayReady: true,
|
||||
},
|
||||
logging: {
|
||||
files: {
|
||||
mpv: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
fs.writeFileSync(appPath, '#!/bin/sh\nexit 0\n');
|
||||
@@ -468,7 +496,9 @@ ${bunBinary} -e "const net=require('node:net'); const fs=require('node:fs'); con
|
||||
const result = runLauncher(['--log-level', 'debug', videoPath], env);
|
||||
|
||||
assert.equal(result.status, 0, `stdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
|
||||
assert.match(fs.readFileSync(mpvArgsPath, 'utf8'), /--script-opts=.*subminer-log_level=debug/);
|
||||
const mpvArgs = fs.readFileSync(mpvArgsPath, 'utf8');
|
||||
assert.match(mpvArgs, /--msg-level=all=warn,subminer=debug/);
|
||||
assert.doesNotMatch(mpvArgs, /--script-opts=.*subminer-log_level=debug/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+20
-1
@@ -1,7 +1,9 @@
|
||||
import path from 'node:path';
|
||||
import packageJson from '../package.json';
|
||||
import { applyLogFileTogglesToEnv } from '../src/shared/log-files.js';
|
||||
import {
|
||||
loadLauncherJellyfinConfig,
|
||||
loadLauncherLoggingConfig,
|
||||
loadLauncherMpvConfig,
|
||||
loadLauncherYoutubeSubgenConfig,
|
||||
parseArgs,
|
||||
@@ -16,6 +18,7 @@ import { runConfigCommand } from './commands/config-command.js';
|
||||
import { runMpvPostAppCommand, runMpvPreAppCommand } from './commands/mpv-command.js';
|
||||
import { runAppPassthroughCommand, runTexthookerCommand } from './commands/app-command.js';
|
||||
import { runDictionaryCommand } from './commands/dictionary-command.js';
|
||||
import { runLogsCommand } from './commands/logs-command.js';
|
||||
import { runStatsCommand } from './commands/stats-command.js';
|
||||
import { runJellyfinCommand } from './commands/jellyfin-command.js';
|
||||
import { runPlaybackCommand } from './commands/playback-command.js';
|
||||
@@ -61,7 +64,19 @@ async function main(): Promise<void> {
|
||||
const scriptName = path.basename(scriptPath);
|
||||
const launcherConfig = loadLauncherYoutubeSubgenConfig();
|
||||
const launcherMpvConfig = loadLauncherMpvConfig();
|
||||
const args = parseArgs(process.argv.slice(2), scriptName, launcherConfig, launcherMpvConfig);
|
||||
const launcherLoggingConfig = loadLauncherLoggingConfig();
|
||||
applyLogFileTogglesToEnv(launcherLoggingConfig.files);
|
||||
process.env.SUBMINER_LOG_ROTATION =
|
||||
launcherLoggingConfig.rotation !== undefined
|
||||
? String(launcherLoggingConfig.rotation)
|
||||
: (process.env.SUBMINER_LOG_ROTATION ?? '7');
|
||||
const args = parseArgs(
|
||||
process.argv.slice(2),
|
||||
scriptName,
|
||||
launcherConfig,
|
||||
launcherMpvConfig,
|
||||
launcherLoggingConfig,
|
||||
);
|
||||
|
||||
if (args.version) {
|
||||
console.log(`SubMiner ${APP_VERSION}`);
|
||||
@@ -87,6 +102,10 @@ async function main(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (runLogsCommand(context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resolvedAppPath = ensureAppPath(context);
|
||||
state.appPath = resolvedAppPath;
|
||||
log('debug', args.logLevel, `Using SubMiner app binary: ${resolvedAppPath}`);
|
||||
|
||||
@@ -566,6 +566,7 @@ function makeArgs(overrides: Partial<Args> = {}): Args {
|
||||
texthookerOpenBrowser: false,
|
||||
useRofi: false,
|
||||
logLevel: 'error',
|
||||
logRotation: 7,
|
||||
passwordStore: '',
|
||||
target: '',
|
||||
targetKind: '',
|
||||
@@ -585,6 +586,7 @@ function makeArgs(overrides: Partial<Args> = {}): Args {
|
||||
stats: false,
|
||||
doctor: false,
|
||||
doctorRefreshKnownWords: false,
|
||||
logsExport: false,
|
||||
version: false,
|
||||
settings: false,
|
||||
configPath: false,
|
||||
|
||||
+36
-10
@@ -4,6 +4,7 @@ import os from 'node:os';
|
||||
import net from 'node:net';
|
||||
import { spawn, spawnSync } from 'node:child_process';
|
||||
import { buildMpvLaunchModeArgs } from '../src/shared/mpv-launch-mode.js';
|
||||
import { buildMpvLoggingArgs } from '../src/shared/mpv-logging-args.js';
|
||||
import {
|
||||
isAppControlServerAvailable as checkAppControlServerAvailable,
|
||||
sendAppControlCommand,
|
||||
@@ -14,7 +15,11 @@ import {
|
||||
type InstalledMpvPluginDetection,
|
||||
} from '../src/main/runtime/first-run-setup-plugin.js';
|
||||
import type { LogLevel, Backend, Args, MpvTrack, PluginRuntimeConfig } from './types.js';
|
||||
import { DEFAULT_MPV_SUBMINER_ARGS, DEFAULT_YOUTUBE_YTDL_FORMAT } from './types.js';
|
||||
import {
|
||||
DEFAULT_MPV_SUBMINER_ARGS,
|
||||
DEFAULT_YOUTUBE_YTDL_FORMAT,
|
||||
shouldForwardLogLevel,
|
||||
} from './types.js';
|
||||
import { appendToAppLog, getAppLogPath, log, fail, getMpvLogPath } from './log.js';
|
||||
import { buildSubminerScriptOpts, resolveAniSkipMetadataForFile } from './aniskip-metadata.js';
|
||||
import { buildPluginRuntimeScriptOptParts } from './config/plugin-runtime-config.js';
|
||||
@@ -951,7 +956,7 @@ export async function startMpv(
|
||||
);
|
||||
}
|
||||
mpvArgs.push(`--script-opts=${scriptOpts}`);
|
||||
mpvArgs.push(`--log-file=${getMpvLogPath()}`);
|
||||
mpvArgs.push(...buildMpvLoggingArgs(args.logLevel, getMpvLogPath(), mpvArgs));
|
||||
|
||||
try {
|
||||
fs.rmSync(socketPath, { force: true });
|
||||
@@ -1031,7 +1036,7 @@ export async function startOverlay(
|
||||
socketPath,
|
||||
...extraAppArgs,
|
||||
];
|
||||
if (args.logLevel !== 'info') overlayArgs.push('--log-level', args.logLevel);
|
||||
if (shouldForwardLogLevel(args.logLevel)) overlayArgs.push('--log-level', args.logLevel);
|
||||
if (args.useTexthooker) overlayArgs.push('--texthooker');
|
||||
|
||||
const controlResult = await sendAppControlCommand(overlayArgs, {
|
||||
@@ -1176,7 +1181,7 @@ export function launchTexthookerOnly(
|
||||
): never {
|
||||
const overlayArgs = ['--texthooker'];
|
||||
if (args.texthookerOpenBrowser) overlayArgs.push('--open-browser');
|
||||
if (args.logLevel !== 'info') overlayArgs.push('--log-level', args.logLevel);
|
||||
if (shouldForwardLogLevel(args.logLevel)) overlayArgs.push('--log-level', args.logLevel);
|
||||
|
||||
log('info', args.logLevel, 'Launching texthooker mode...');
|
||||
const result = runSyncAppCommand(appPath, overlayArgs, true);
|
||||
@@ -1254,7 +1259,7 @@ function stopManagedOverlayApp(args: Args): void {
|
||||
log('info', args.logLevel, 'Stopping SubMiner overlay...');
|
||||
|
||||
const stopArgs = ['--stop'];
|
||||
if (args.logLevel !== 'info') stopArgs.push('--log-level', args.logLevel);
|
||||
if (shouldForwardLogLevel(args.logLevel)) stopArgs.push('--log-level', args.logLevel);
|
||||
|
||||
const target = resolveAppSpawnTarget(state.appPath, stopArgs);
|
||||
const result = spawnSync(target.command, target.args, {
|
||||
@@ -1306,6 +1311,8 @@ function buildAppEnv(
|
||||
...baseEnv,
|
||||
SUBMINER_APP_LOG: getAppLogPath(),
|
||||
SUBMINER_MPV_LOG: getMpvLogPath(),
|
||||
SUBMINER_LOG_LEVEL: extraEnv.SUBMINER_LOG_LEVEL ?? baseEnv.SUBMINER_LOG_LEVEL,
|
||||
SUBMINER_LOG_ROTATION: extraEnv.SUBMINER_LOG_ROTATION ?? baseEnv.SUBMINER_LOG_ROTATION,
|
||||
};
|
||||
delete env.ELECTRON_RUN_AS_NODE;
|
||||
clearTransportedAppArgs(env);
|
||||
@@ -1326,10 +1333,13 @@ function buildAppEnv(
|
||||
}
|
||||
|
||||
export function buildMpvEnv(
|
||||
args: Pick<Args, 'backend'>,
|
||||
args: Pick<Args, 'backend' | 'logLevel' | 'logRotation'>,
|
||||
baseEnv: NodeJS.ProcessEnv = process.env,
|
||||
): NodeJS.ProcessEnv {
|
||||
const env = buildAppEnv(baseEnv);
|
||||
const env = buildAppEnv(baseEnv, {
|
||||
SUBMINER_LOG_LEVEL: args.logLevel,
|
||||
SUBMINER_LOG_ROTATION: String(args.logRotation),
|
||||
});
|
||||
if (!shouldForceX11MpvBackend(args, env)) {
|
||||
return env;
|
||||
}
|
||||
@@ -1586,13 +1596,13 @@ export function runAppCommandWithInheritLogged(
|
||||
|
||||
export function launchAppStartDetached(appPath: string, logLevel: LogLevel): void {
|
||||
const startArgs = ['--start'];
|
||||
if (logLevel !== 'info') startArgs.push('--log-level', logLevel);
|
||||
if (shouldForwardLogLevel(logLevel)) startArgs.push('--log-level', logLevel);
|
||||
launchAppCommandDetached(appPath, startArgs, logLevel, 'start');
|
||||
}
|
||||
|
||||
export function launchAppBackgroundDetached(appPath: string, logLevel: LogLevel): void {
|
||||
const startArgs = ['--start', '--background'];
|
||||
if (logLevel !== 'info') startArgs.push('--log-level', logLevel);
|
||||
if (shouldForwardLogLevel(logLevel)) startArgs.push('--log-level', logLevel);
|
||||
launchAppCommandDetached(appPath, startArgs, logLevel, 'app', {
|
||||
[BACKGROUND_CHILD_ENV]: '1',
|
||||
});
|
||||
@@ -1615,6 +1625,22 @@ export function launchAppCommandDetached(
|
||||
`${label}: launching detached app with args: ${[target.command, ...target.args].join(' ')}`,
|
||||
);
|
||||
const appLogPath = getAppLogPath();
|
||||
if (!appLogPath) {
|
||||
try {
|
||||
const proc = spawn(target.command, target.args, {
|
||||
stdio: 'ignore',
|
||||
detached: true,
|
||||
env: buildAppEnv(process.env, { ...target.env, ...extraEnv }),
|
||||
});
|
||||
proc.once('error', (error) => {
|
||||
log('warn', logLevel, `${label}: failed to launch detached app: ${error.message}`);
|
||||
});
|
||||
proc.unref();
|
||||
} catch (error) {
|
||||
log('warn', logLevel, `${label}: failed to launch detached app: ${(error as Error).message}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
fs.mkdirSync(path.dirname(appLogPath), { recursive: true });
|
||||
const stdoutFd = fs.openSync(appLogPath, 'a');
|
||||
const stderrFd = fs.openSync(appLogPath, 'a');
|
||||
@@ -1673,7 +1699,7 @@ export function launchMpvIdleDetached(
|
||||
runtimeScriptOpts,
|
||||
)}`,
|
||||
);
|
||||
mpvArgs.push(`--log-file=${getMpvLogPath()}`);
|
||||
mpvArgs.push(...buildMpvLoggingArgs(args.logLevel, getMpvLogPath(), mpvArgs));
|
||||
mpvArgs.push(`--input-ipc-server=${socketPath}`);
|
||||
const mpvTarget = resolveCommandInvocation('mpv', mpvArgs, {
|
||||
normalizeWindowsShellArgs: false,
|
||||
|
||||
@@ -244,3 +244,18 @@ test('parseArgs maps doctor refresh-known-words flag', () => {
|
||||
assert.equal(parsed.doctor, true);
|
||||
assert.equal(parsed.doctorRefreshKnownWords, true);
|
||||
});
|
||||
|
||||
test('parseArgs maps logs export flag', () => {
|
||||
const parsed = parseArgs(['logs', '-e'], 'subminer', {});
|
||||
|
||||
assert.equal(parsed.logsExport, true);
|
||||
});
|
||||
|
||||
test('parseArgs requires an explicit logs action', () => {
|
||||
const exit = withProcessExitIntercept(() => {
|
||||
parseArgs(['logs'], 'subminer', {});
|
||||
});
|
||||
|
||||
assert.equal(exit.code, 1);
|
||||
assert.match(exit.stderr, /Logs command requires -e or --export/);
|
||||
});
|
||||
|
||||
@@ -23,6 +23,8 @@ type SmokeCase = {
|
||||
artifactsDir: string;
|
||||
binDir: string;
|
||||
xdgConfigHome: string;
|
||||
appDataDir: string;
|
||||
localAppDataDir: string;
|
||||
homeDir: string;
|
||||
socketDir: string;
|
||||
socketPath: string;
|
||||
@@ -61,6 +63,8 @@ function createSmokeCase(name: string): SmokeCase {
|
||||
const artifactsDir = path.join(root, 'artifacts');
|
||||
const binDir = path.join(root, 'bin');
|
||||
const xdgConfigHome = path.join(root, 'xdg');
|
||||
const appDataDir = path.join(root, 'AppData', 'Roaming');
|
||||
const localAppDataDir = path.join(root, 'AppData', 'Local');
|
||||
const homeDir = path.join(root, 'home');
|
||||
const socketDir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-smoke-sock-'));
|
||||
const socketPath = path.join(socketDir, 'subminer.sock');
|
||||
@@ -73,7 +77,7 @@ function createSmokeCase(name: string): SmokeCase {
|
||||
fs.mkdirSync(binDir, { recursive: true });
|
||||
fs.writeFileSync(videoPath, 'fake video fixture');
|
||||
|
||||
const configDir = getDefaultConfigDir({ xdgConfigHome, homeDir });
|
||||
const configDir = getDefaultConfigDir({ xdgConfigHome, appDataDir, homeDir });
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(configDir, 'config.jsonc'), JSON.stringify({ mpv: { socketPath } }));
|
||||
const setupState = createDefaultSetupState();
|
||||
@@ -159,6 +163,8 @@ process.exit(0);
|
||||
artifactsDir,
|
||||
binDir,
|
||||
xdgConfigHome,
|
||||
appDataDir,
|
||||
localAppDataDir,
|
||||
homeDir,
|
||||
socketDir,
|
||||
socketPath,
|
||||
@@ -174,6 +180,8 @@ function makeTestEnv(smokeCase: SmokeCase): NodeJS.ProcessEnv {
|
||||
...process.env,
|
||||
HOME: smokeCase.homeDir,
|
||||
XDG_CONFIG_HOME: smokeCase.xdgConfigHome,
|
||||
APPDATA: smokeCase.appDataDir,
|
||||
LOCALAPPDATA: smokeCase.localAppDataDir,
|
||||
SUBMINER_APPIMAGE_PATH: smokeCase.fakeAppPath,
|
||||
SUBMINER_MPV_LOG: smokeCase.mpvOverlayLogPath,
|
||||
};
|
||||
@@ -410,7 +418,7 @@ test(
|
||||
const env = makeTestEnv(smokeCase);
|
||||
const result = runLauncher(
|
||||
smokeCase,
|
||||
['--backend', 'x11', '--start-overlay', smokeCase.videoPath],
|
||||
['--backend', 'x11', '--log-level', 'info', '--start-overlay', smokeCase.videoPath],
|
||||
env,
|
||||
'overlay-start-stop',
|
||||
);
|
||||
|
||||
+17
-1
@@ -1,7 +1,11 @@
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import type { MpvBackend, MpvLaunchMode } from '../src/types/config.js';
|
||||
import { resolveDefaultLogFilePath } from '../src/shared/log-files.js';
|
||||
import {
|
||||
resolveDefaultLogFilePath,
|
||||
type LogFileToggles,
|
||||
type LogRotation,
|
||||
} from '../src/shared/log-files.js';
|
||||
export { VIDEO_EXTENSIONS } from '../src/shared/video-extensions.js';
|
||||
|
||||
export const ROFI_THEME_FILE = 'subminer.rasi';
|
||||
@@ -67,6 +71,9 @@ export const DEFAULT_MPV_SUBMINER_ARGS = [
|
||||
] as const;
|
||||
|
||||
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
||||
export function shouldForwardLogLevel(level: LogLevel): boolean {
|
||||
return level === 'debug' || level === 'error';
|
||||
}
|
||||
export type Backend = 'auto' | 'hyprland' | 'sway' | 'x11' | 'macos' | 'windows';
|
||||
export type JimakuLanguagePreference = 'ja' | 'en' | 'none';
|
||||
|
||||
@@ -106,6 +113,7 @@ export interface Args {
|
||||
texthookerOpenBrowser: boolean;
|
||||
useRofi: boolean;
|
||||
logLevel: LogLevel;
|
||||
logRotation: LogRotation;
|
||||
passwordStore: string;
|
||||
target: string;
|
||||
targetKind: '' | 'file' | 'url';
|
||||
@@ -132,6 +140,7 @@ export interface Args {
|
||||
dictionaryTarget?: string;
|
||||
doctor: boolean;
|
||||
doctorRefreshKnownWords: boolean;
|
||||
logsExport: boolean;
|
||||
version: boolean;
|
||||
update?: boolean;
|
||||
settings: boolean;
|
||||
@@ -186,10 +195,17 @@ export interface LauncherMpvConfig {
|
||||
aniskipButtonKey?: string;
|
||||
}
|
||||
|
||||
export interface LauncherLoggingConfig {
|
||||
level?: LogLevel;
|
||||
rotation?: LogRotation;
|
||||
files?: Partial<LogFileToggles>;
|
||||
}
|
||||
|
||||
export interface PluginRuntimeConfig {
|
||||
socketPath: string;
|
||||
binaryPath: string;
|
||||
backend: Backend;
|
||||
logLevel?: LogLevel;
|
||||
autoStart: boolean;
|
||||
autoStartVisibleOverlay: boolean;
|
||||
autoStartPauseUntilReady: boolean;
|
||||
|
||||
Reference in New Issue
Block a user