mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-26 00:55:16 -07:00
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:
@@ -0,0 +1,4 @@
|
|||||||
|
type: added
|
||||||
|
area: launcher
|
||||||
|
|
||||||
|
- Added `subminer --version` and `subminer -v` to print the installed SubMiner app version.
|
||||||
@@ -99,6 +99,7 @@ Use `subminer <subcommand> -h` for command-specific help.
|
|||||||
| `-r, --recursive` | Search directories recursively |
|
| `-r, --recursive` | Search directories recursively |
|
||||||
| `-R, --rofi` | Use rofi instead of fzf |
|
| `-R, --rofi` | Use rofi instead of fzf |
|
||||||
| `--setup` | Open first-run setup popup manually |
|
| `--setup` | Open first-run setup popup manually |
|
||||||
|
| `-v, --version` | Print installed SubMiner version |
|
||||||
| `-u, --update` | Check for SubMiner updates and update the app/launcher when possible |
|
| `-u, --update` | Check for SubMiner updates and update the app/launcher when possible |
|
||||||
| `--start` | Explicitly start overlay after mpv launches |
|
| `--start` | Explicitly start overlay after mpv launches |
|
||||||
| `-S, --start-overlay` | Explicitly start overlay after mpv launches |
|
| `-S, --start-overlay` | Explicitly start overlay after mpv launches |
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ subminer -S video.mkv # Same as above via --start-overlay
|
|||||||
subminer https://youtu.be/... # Play a YouTube URL
|
subminer https://youtu.be/... # Play a YouTube URL
|
||||||
subminer ytsearch:"jp news" # Play first YouTube search result
|
subminer ytsearch:"jp news" # Play first YouTube search result
|
||||||
subminer --setup # Open first-run setup popup
|
subminer --setup # Open first-run setup popup
|
||||||
|
subminer --version # Print installed SubMiner version
|
||||||
|
subminer -v # Same as above
|
||||||
subminer --log-level debug video.mkv # Enable verbose logs for launch/debugging
|
subminer --log-level debug video.mkv # Enable verbose logs for launch/debugging
|
||||||
subminer --log-level warn video.mkv # Set logging level explicitly
|
subminer --log-level warn video.mkv # Set logging level explicitly
|
||||||
subminer --args '--fs=opengl-hq --ytdl-format=bestvideo*+bestaudio/best' video.mkv # Pass extra mpv args
|
subminer --args '--fs=opengl-hq --ytdl-format=bestvideo*+bestaudio/best' video.mkv # Pass extra mpv args
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ function createContext(): LauncherCommandContext {
|
|||||||
stats: false,
|
stats: false,
|
||||||
doctor: false,
|
doctor: false,
|
||||||
doctorRefreshKnownWords: false,
|
doctorRefreshKnownWords: false,
|
||||||
|
version: false,
|
||||||
configPath: false,
|
configPath: false,
|
||||||
configShow: false,
|
configShow: false,
|
||||||
mpvIdle: false,
|
mpvIdle: false,
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ export function createDefaultArgs(
|
|||||||
statsCleanupLifetime: false,
|
statsCleanupLifetime: false,
|
||||||
doctor: false,
|
doctor: false,
|
||||||
doctorRefreshKnownWords: false,
|
doctorRefreshKnownWords: false,
|
||||||
|
version: false,
|
||||||
update: false,
|
update: false,
|
||||||
configPath: false,
|
configPath: false,
|
||||||
configShow: false,
|
configShow: false,
|
||||||
@@ -219,6 +220,7 @@ export function applyRootOptionsToArgs(
|
|||||||
if (typeof options.passwordStore === 'string') parsed.passwordStore = options.passwordStore;
|
if (typeof options.passwordStore === 'string') parsed.passwordStore = options.passwordStore;
|
||||||
if (options.rofi === true) parsed.useRofi = true;
|
if (options.rofi === true) parsed.useRofi = true;
|
||||||
if (options.update === true) parsed.update = true;
|
if (options.update === true) parsed.update = true;
|
||||||
|
if (options.version === true) parsed.version = true;
|
||||||
if (options.startOverlay === true) parsed.autoStartOverlay = true;
|
if (options.startOverlay === true) parsed.autoStartOverlay = true;
|
||||||
if (options.texthooker === false) parsed.useTexthooker = false;
|
if (options.texthooker === false) parsed.useTexthooker = false;
|
||||||
if (typeof options.args === 'string') parsed.mpvArgs = options.args;
|
if (typeof options.args === 'string') parsed.mpvArgs = options.args;
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ function applyRootOptions(program: Command): void {
|
|||||||
.option('-p, --profile <profile>', 'MPV profile')
|
.option('-p, --profile <profile>', 'MPV profile')
|
||||||
.option('--start', 'Explicitly start overlay')
|
.option('--start', 'Explicitly start overlay')
|
||||||
.option('--log-level <level>', 'Log level')
|
.option('--log-level <level>', 'Log level')
|
||||||
|
.option('-v, --version', 'Show SubMiner version')
|
||||||
.option('-u, --update', 'Check for updates')
|
.option('-u, --update', 'Check for updates')
|
||||||
.option('-R, --rofi', 'Use rofi picker')
|
.option('-R, --rofi', 'Use rofi picker')
|
||||||
.option('-S, --start-overlay', 'Auto-start overlay')
|
.option('-S, --start-overlay', 'Auto-start overlay')
|
||||||
|
|||||||
@@ -99,6 +99,30 @@ test('config discovery ignores lowercase subminer candidate', () => {
|
|||||||
assert.equal(resolved, expected);
|
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', () => {
|
test('config path prefers jsonc over json for same directory', () => {
|
||||||
withTempDir((root) => {
|
withTempDir((root) => {
|
||||||
const homeDir = path.join(root, 'home');
|
const homeDir = path.join(root, 'home');
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import packageJson from '../package.json';
|
||||||
import {
|
import {
|
||||||
loadLauncherJellyfinConfig,
|
loadLauncherJellyfinConfig,
|
||||||
loadLauncherMpvConfig,
|
loadLauncherMpvConfig,
|
||||||
@@ -20,6 +21,11 @@ import { runJellyfinCommand } from './commands/jellyfin-command.js';
|
|||||||
import { runPlaybackCommand } from './commands/playback-command.js';
|
import { runPlaybackCommand } from './commands/playback-command.js';
|
||||||
import { runUpdateCommand } from './commands/update-command.js';
|
import { runUpdateCommand } from './commands/update-command.js';
|
||||||
|
|
||||||
|
const APP_VERSION =
|
||||||
|
typeof packageJson.version === 'string' && packageJson.version.trim()
|
||||||
|
? packageJson.version
|
||||||
|
: 'unknown';
|
||||||
|
|
||||||
function createCommandContext(
|
function createCommandContext(
|
||||||
args: ReturnType<typeof parseArgs>,
|
args: ReturnType<typeof parseArgs>,
|
||||||
scriptPath: string,
|
scriptPath: string,
|
||||||
@@ -56,6 +62,12 @@ async function main(): Promise<void> {
|
|||||||
const launcherConfig = loadLauncherYoutubeSubgenConfig();
|
const launcherConfig = loadLauncherYoutubeSubgenConfig();
|
||||||
const launcherMpvConfig = loadLauncherMpvConfig();
|
const launcherMpvConfig = loadLauncherMpvConfig();
|
||||||
const args = parseArgs(process.argv.slice(2), scriptName, launcherConfig, launcherMpvConfig);
|
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 pluginRuntimeConfig = readPluginRuntimeConfig(args.logLevel);
|
||||||
const appPath = findAppBinary(scriptPath);
|
const appPath = findAppBinary(scriptPath);
|
||||||
|
|
||||||
|
|||||||
@@ -529,6 +529,7 @@ function makeArgs(overrides: Partial<Args> = {}): Args {
|
|||||||
stats: false,
|
stats: false,
|
||||||
doctor: false,
|
doctor: false,
|
||||||
doctorRefreshKnownWords: false,
|
doctorRefreshKnownWords: false,
|
||||||
|
version: false,
|
||||||
configPath: false,
|
configPath: false,
|
||||||
configShow: false,
|
configShow: false,
|
||||||
mpvIdle: false,
|
mpvIdle: false,
|
||||||
|
|||||||
@@ -69,6 +69,17 @@ test('parseArgs maps root update flags without conflicting with jellyfin usernam
|
|||||||
assert.equal(jellyfinParsed.jellyfinUsername, 'kyle');
|
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', () => {
|
test('parseArgs maps jellyfin play action and log-level override', () => {
|
||||||
const parsed = parseArgs(['jellyfin', 'play', '--log-level', 'debug'], 'subminer', {});
|
const parsed = parseArgs(['jellyfin', 'play', '--log-level', 'debug'], 'subminer', {});
|
||||||
|
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ export interface Args {
|
|||||||
dictionaryTarget?: string;
|
dictionaryTarget?: string;
|
||||||
doctor: boolean;
|
doctor: boolean;
|
||||||
doctorRefreshKnownWords: boolean;
|
doctorRefreshKnownWords: boolean;
|
||||||
|
version: boolean;
|
||||||
update?: boolean;
|
update?: boolean;
|
||||||
configPath: boolean;
|
configPath: boolean;
|
||||||
configShow: boolean;
|
configShow: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user