mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-27 00:55:16 -07:00
feat: add auto update support
This commit is contained in:
@@ -109,6 +109,58 @@ test('loads defaults when config is missing', () => {
|
||||
assert.equal(config.immersionTracking.lifetimeSummaries?.anime, true);
|
||||
assert.equal(config.immersionTracking.lifetimeSummaries?.media, true);
|
||||
assert.equal(config.stats.autoOpenBrowser, false);
|
||||
assert.equal(config.updates.enabled, true);
|
||||
assert.equal(config.updates.checkIntervalHours, 24);
|
||||
assert.equal(config.updates.notificationType, 'system');
|
||||
assert.equal(config.updates.channel, 'stable');
|
||||
});
|
||||
|
||||
test('parses updates config and warns on invalid values', () => {
|
||||
const validDir = makeTempDir();
|
||||
fs.writeFileSync(
|
||||
path.join(validDir, 'config.jsonc'),
|
||||
`{
|
||||
"updates": {
|
||||
"enabled": false,
|
||||
"checkIntervalHours": 6,
|
||||
"notificationType": "both",
|
||||
"channel": "prerelease"
|
||||
}
|
||||
}`,
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
const validService = new ConfigService(validDir);
|
||||
assert.equal(validService.getConfig().updates.enabled, false);
|
||||
assert.equal(validService.getConfig().updates.checkIntervalHours, 6);
|
||||
assert.equal(validService.getConfig().updates.notificationType, 'both');
|
||||
assert.equal(validService.getConfig().updates.channel, 'prerelease');
|
||||
|
||||
const invalidDir = makeTempDir();
|
||||
fs.writeFileSync(
|
||||
path.join(invalidDir, 'config.jsonc'),
|
||||
`{
|
||||
"updates": {
|
||||
"enabled": "yes",
|
||||
"checkIntervalHours": 0,
|
||||
"notificationType": "toast",
|
||||
"channel": "nightly"
|
||||
}
|
||||
}`,
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
const invalidService = new ConfigService(invalidDir);
|
||||
const config = invalidService.getConfig();
|
||||
const warnings = invalidService.getWarnings();
|
||||
assert.equal(config.updates.enabled, DEFAULT_CONFIG.updates.enabled);
|
||||
assert.equal(config.updates.checkIntervalHours, DEFAULT_CONFIG.updates.checkIntervalHours);
|
||||
assert.equal(config.updates.notificationType, DEFAULT_CONFIG.updates.notificationType);
|
||||
assert.equal(config.updates.channel, DEFAULT_CONFIG.updates.channel);
|
||||
assert.ok(warnings.some((warning) => warning.path === 'updates.enabled'));
|
||||
assert.ok(warnings.some((warning) => warning.path === 'updates.checkIntervalHours'));
|
||||
assert.ok(warnings.some((warning) => warning.path === 'updates.notificationType'));
|
||||
assert.ok(warnings.some((warning) => warning.path === 'updates.channel'));
|
||||
});
|
||||
|
||||
test('throws actionable startup parse error for malformed config at construction time', () => {
|
||||
@@ -2124,6 +2176,7 @@ test('template generator includes known keys', () => {
|
||||
assert.match(output, /"websocket":/);
|
||||
assert.match(output, /"discordPresence":/);
|
||||
assert.match(output, /"startupWarmups":/);
|
||||
assert.match(output, /"updates":/);
|
||||
assert.match(output, /"youtube":/);
|
||||
assert.doesNotMatch(output, /"youtubeSubgen":/);
|
||||
assert.match(output, /"characterDictionary":\s*\{/);
|
||||
@@ -2210,6 +2263,14 @@ test('template generator includes known keys', () => {
|
||||
output,
|
||||
/"autoOpenBrowser": false,? \/\/ Automatically open the stats dashboard in a browser when the server starts\. Values: true \| false/,
|
||||
);
|
||||
assert.match(
|
||||
output,
|
||||
/"notificationType": "system",? \/\/ How SubMiner announces available updates\. Values: system \| osd \| both \| none/,
|
||||
);
|
||||
assert.match(
|
||||
output,
|
||||
/"channel": "stable",? \/\/ Release channel used for update checks\. Values: stable \| prerelease/,
|
||||
);
|
||||
assert.match(
|
||||
output,
|
||||
/"primarySubLanguages": \[\s*"ja",\s*"jpn"\s*\],? \/\/ Comma-separated primary subtitle language priority for managed subtitle auto-selection\./,
|
||||
|
||||
@@ -33,6 +33,7 @@ const {
|
||||
youtube,
|
||||
subsync,
|
||||
startupWarmups,
|
||||
updates,
|
||||
auto_start_overlay,
|
||||
} = CORE_DEFAULT_CONFIG;
|
||||
const { ankiConnect, jimaku, anilist, mpv, yomitan, jellyfin, discordPresence, ai, youtubeSubgen } =
|
||||
@@ -55,6 +56,7 @@ export const DEFAULT_CONFIG: ResolvedConfig = {
|
||||
youtube,
|
||||
subsync,
|
||||
startupWarmups,
|
||||
updates,
|
||||
subtitleStyle,
|
||||
subtitleSidebar,
|
||||
auto_start_overlay,
|
||||
|
||||
@@ -14,6 +14,7 @@ export const CORE_DEFAULT_CONFIG: Pick<
|
||||
| 'youtube'
|
||||
| 'subsync'
|
||||
| 'startupWarmups'
|
||||
| 'updates'
|
||||
| 'auto_start_overlay'
|
||||
> = {
|
||||
subtitlePosition: { yPercent: 10 },
|
||||
@@ -116,5 +117,11 @@ export const CORE_DEFAULT_CONFIG: Pick<
|
||||
subtitleDictionaries: true,
|
||||
jellyfinRemoteSession: true,
|
||||
},
|
||||
updates: {
|
||||
enabled: true,
|
||||
checkIntervalHours: 24,
|
||||
notificationType: 'system',
|
||||
channel: 'stable',
|
||||
},
|
||||
auto_start_overlay: false,
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ test('config option registry includes critical paths and has unique entries', ()
|
||||
'controller.enabled',
|
||||
'controller.scrollPixelsPerSecond',
|
||||
'startupWarmups.lowPowerMode',
|
||||
'updates.channel',
|
||||
'youtube.primarySubLanguages',
|
||||
'subtitleStyle.enableJlpt',
|
||||
'subtitleStyle.autoPauseVideoOnYomitanPopup',
|
||||
|
||||
@@ -383,6 +383,32 @@ export function buildCoreConfigOptionRegistry(
|
||||
defaultValue: defaultConfig.startupWarmups.jellyfinRemoteSession,
|
||||
description: 'Warm up Jellyfin remote session at startup.',
|
||||
},
|
||||
{
|
||||
path: 'updates.enabled',
|
||||
kind: 'boolean',
|
||||
defaultValue: defaultConfig.updates.enabled,
|
||||
description: 'Run automatic update checks in the background.',
|
||||
},
|
||||
{
|
||||
path: 'updates.checkIntervalHours',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.updates.checkIntervalHours,
|
||||
description: 'Minimum hours between automatic update checks.',
|
||||
},
|
||||
{
|
||||
path: 'updates.notificationType',
|
||||
kind: 'enum',
|
||||
enumValues: ['system', 'osd', 'both', 'none'],
|
||||
defaultValue: defaultConfig.updates.notificationType,
|
||||
description: 'How SubMiner announces available updates.',
|
||||
},
|
||||
{
|
||||
path: 'updates.channel',
|
||||
kind: 'enum',
|
||||
enumValues: ['stable', 'prerelease'],
|
||||
defaultValue: defaultConfig.updates.channel,
|
||||
description: 'Release channel used for update checks.',
|
||||
},
|
||||
{
|
||||
path: 'shortcuts.multiCopyTimeoutMs',
|
||||
kind: 'number',
|
||||
|
||||
@@ -53,6 +53,14 @@ const CORE_TEMPLATE_SECTIONS: ConfigTemplateSection[] = [
|
||||
],
|
||||
key: 'startupWarmups',
|
||||
},
|
||||
{
|
||||
title: 'Updates',
|
||||
description: [
|
||||
'Automatic update check behavior.',
|
||||
'Manual checks from the tray or launcher are always allowed.',
|
||||
],
|
||||
key: 'updates',
|
||||
},
|
||||
{
|
||||
title: 'Keyboard Shortcuts',
|
||||
description: ['Overlay keyboard shortcuts. Set a shortcut to null to disable.'],
|
||||
|
||||
@@ -478,6 +478,62 @@ export function applyCoreDomainConfig(context: ResolveContext): void {
|
||||
}
|
||||
}
|
||||
|
||||
if (isObject(src.updates)) {
|
||||
const enabled = asBoolean(src.updates.enabled);
|
||||
if (enabled !== undefined) {
|
||||
resolved.updates.enabled = enabled;
|
||||
} else if (src.updates.enabled !== undefined) {
|
||||
warn('updates.enabled', src.updates.enabled, resolved.updates.enabled, 'Expected boolean.');
|
||||
}
|
||||
|
||||
const checkIntervalHours = asNumber(src.updates.checkIntervalHours);
|
||||
if (
|
||||
checkIntervalHours !== undefined &&
|
||||
Number.isFinite(checkIntervalHours) &&
|
||||
checkIntervalHours > 0
|
||||
) {
|
||||
resolved.updates.checkIntervalHours = checkIntervalHours;
|
||||
} else if (src.updates.checkIntervalHours !== undefined) {
|
||||
warn(
|
||||
'updates.checkIntervalHours',
|
||||
src.updates.checkIntervalHours,
|
||||
resolved.updates.checkIntervalHours,
|
||||
'Expected positive number.',
|
||||
);
|
||||
}
|
||||
|
||||
const notificationType = asString(src.updates.notificationType);
|
||||
if (
|
||||
notificationType === 'system' ||
|
||||
notificationType === 'osd' ||
|
||||
notificationType === 'both' ||
|
||||
notificationType === 'none'
|
||||
) {
|
||||
resolved.updates.notificationType = notificationType;
|
||||
} else if (src.updates.notificationType !== undefined) {
|
||||
warn(
|
||||
'updates.notificationType',
|
||||
src.updates.notificationType,
|
||||
resolved.updates.notificationType,
|
||||
'Expected system, osd, both, or none.',
|
||||
);
|
||||
}
|
||||
|
||||
const channel = asString(src.updates.channel);
|
||||
if (channel === 'stable' || channel === 'prerelease') {
|
||||
resolved.updates.channel = channel;
|
||||
} else if (src.updates.channel !== undefined) {
|
||||
warn(
|
||||
'updates.channel',
|
||||
src.updates.channel,
|
||||
resolved.updates.channel,
|
||||
'Expected stable or prerelease.',
|
||||
);
|
||||
}
|
||||
} else if (src.updates !== undefined) {
|
||||
warn('updates', src.updates, resolved.updates, 'Expected object.');
|
||||
}
|
||||
|
||||
if (isObject(src.shortcuts)) {
|
||||
const shortcutKeys = [
|
||||
'toggleVisibleOverlayGlobal',
|
||||
|
||||
Reference in New Issue
Block a user