feat(launcher): add --version / -v flag to print app version

- Exits early with `SubMiner <version>` output, no app binary required
- Maps `-v` at root level without conflicting with `stats cleanup -v`
- Adds `version: boolean` to `Args` type and default args
- Documents flag in docs-site and adds changelog fragment
This commit is contained in:
2026-05-16 22:47:57 -07:00
parent 4d010e6a18
commit 6ca5cede3e
11 changed files with 60 additions and 0 deletions
@@ -52,6 +52,7 @@ function createContext(): LauncherCommandContext {
stats: false,
doctor: false,
doctorRefreshKnownWords: false,
version: false,
configPath: false,
configShow: false,
mpvIdle: false,
+2
View File
@@ -156,6 +156,7 @@ export function createDefaultArgs(
statsCleanupLifetime: false,
doctor: false,
doctorRefreshKnownWords: false,
version: false,
update: false,
configPath: false,
configShow: false,
@@ -219,6 +220,7 @@ export function applyRootOptionsToArgs(
if (typeof options.passwordStore === 'string') parsed.passwordStore = options.passwordStore;
if (options.rofi === true) parsed.useRofi = true;
if (options.update === true) parsed.update = true;
if (options.version === true) parsed.version = true;
if (options.startOverlay === true) parsed.autoStartOverlay = true;
if (options.texthooker === false) parsed.useTexthooker = false;
if (typeof options.args === 'string') parsed.mpvArgs = options.args;
+1
View File
@@ -57,6 +57,7 @@ function applyRootOptions(program: Command): void {
.option('-p, --profile <profile>', 'MPV profile')
.option('--start', 'Explicitly start overlay')
.option('--log-level <level>', 'Log level')
.option('-v, --version', 'Show SubMiner version')
.option('-u, --update', 'Check for updates')
.option('-R, --rofi', 'Use rofi picker')
.option('-S, --start-overlay', 'Auto-start overlay')
+24
View File
@@ -99,6 +99,30 @@ test('config discovery ignores lowercase subminer candidate', () => {
assert.equal(resolved, expected);
});
test('version flag prints installed app version without requiring app binary', () => {
withTempDir((root) => {
const homeDir = path.join(root, 'home');
const xdgConfigHome = path.join(root, 'xdg');
const result = runLauncher(['--version'], makeTestEnv(homeDir, xdgConfigHome));
assert.equal(result.status, 0);
assert.match(result.stdout.trim(), /^SubMiner \d+\.\d+\.\d+/);
assert.equal(result.stderr, '');
});
});
test('short version flag prints installed app version without requiring app binary', () => {
withTempDir((root) => {
const homeDir = path.join(root, 'home');
const xdgConfigHome = path.join(root, 'xdg');
const result = runLauncher(['-v'], makeTestEnv(homeDir, xdgConfigHome));
assert.equal(result.status, 0);
assert.match(result.stdout.trim(), /^SubMiner \d+\.\d+\.\d+/);
assert.equal(result.stderr, '');
});
});
test('config path prefers jsonc over json for same directory', () => {
withTempDir((root) => {
const homeDir = path.join(root, 'home');
+12
View File
@@ -1,4 +1,5 @@
import path from 'node:path';
import packageJson from '../package.json';
import {
loadLauncherJellyfinConfig,
loadLauncherMpvConfig,
@@ -20,6 +21,11 @@ import { runJellyfinCommand } from './commands/jellyfin-command.js';
import { runPlaybackCommand } from './commands/playback-command.js';
import { runUpdateCommand } from './commands/update-command.js';
const APP_VERSION =
typeof packageJson.version === 'string' && packageJson.version.trim()
? packageJson.version
: 'unknown';
function createCommandContext(
args: ReturnType<typeof parseArgs>,
scriptPath: string,
@@ -56,6 +62,12 @@ async function main(): Promise<void> {
const launcherConfig = loadLauncherYoutubeSubgenConfig();
const launcherMpvConfig = loadLauncherMpvConfig();
const args = parseArgs(process.argv.slice(2), scriptName, launcherConfig, launcherMpvConfig);
if (args.version) {
console.log(`SubMiner ${APP_VERSION}`);
return;
}
const pluginRuntimeConfig = readPluginRuntimeConfig(args.logLevel);
const appPath = findAppBinary(scriptPath);
+1
View File
@@ -529,6 +529,7 @@ function makeArgs(overrides: Partial<Args> = {}): Args {
stats: false,
doctor: false,
doctorRefreshKnownWords: false,
version: false,
configPath: false,
configShow: false,
mpvIdle: false,
+11
View File
@@ -69,6 +69,17 @@ test('parseArgs maps root update flags without conflicting with jellyfin usernam
assert.equal(jellyfinParsed.jellyfinUsername, 'kyle');
});
test('parseArgs maps root version flags without conflicting with stats vocab flag', () => {
const shortParsed = parseArgs(['-v'], 'subminer', {});
const longParsed = parseArgs(['--version'], 'subminer', {});
const statsParsed = parseArgs(['stats', 'cleanup', '-v'], 'subminer', {});
assert.equal(shortParsed.version, true);
assert.equal(longParsed.version, true);
assert.equal(statsParsed.version, false);
assert.equal(statsParsed.statsCleanupVocab, true);
});
test('parseArgs maps jellyfin play action and log-level override', () => {
const parsed = parseArgs(['jellyfin', 'play', '--log-level', 'debug'], 'subminer', {});
+1
View File
@@ -134,6 +134,7 @@ export interface Args {
dictionaryTarget?: string;
doctor: boolean;
doctorRefreshKnownWords: boolean;
version: boolean;
update?: boolean;
configPath: boolean;
configShow: boolean;