import { createRequire } from 'node:module';
import commandLineArgs from 'command-line-args';
import type { AppConfig } from '../models/config.js';
import type { ArchiveMode, ArchiveOptions } from '../models/fileOperation.js';
import { expandTilde } from '../utils/path.js';
const _require = createRequire(import.meta.url);
const { version: PKG_VERSION } = _require('../../package.json') as { version: string };
const VALID_MODES: ArchiveMode[] = ['copy', 'move', 'date'];
export const optionDefinitions = [
{ name: 'source', alias: 's', type: String, description: 'Source directory to scan (required)' },
{ name: 'target', alias: 't', type: String, description: 'Target directory where files are archived (required)' },
{ name: 'mode', alias: 'm', type: String, description: 'Operation mode: copy, move, or date (default: copy)' },
{ name: 'dry-run', type: Boolean, description: 'Preview operations without writing any files' },
{ name: 'recurse', alias: 'r', type: Boolean, description: 'Scan subdirectories of --source recursively' },
{ name: 'verbose', alias: 'v', type: Boolean, description: 'Print detailed logs for every file operation' },
{ name: 'version', type: Boolean, description: 'Print the current version and exit' },
{ name: 'help', alias: 'h', type: Boolean, description: 'Display this help message and exit' },
];
/**
* Prints usage information to stdout.
*/
export function displayHelp(): void {
const lines: string[] = [
`media-archiver v${PKG_VERSION}`,
'',
'Usage: media-archiver --source
--target [options]',
'',
'Options:',
];
for (const def of optionDefinitions) {
const long = `--${def.name}`;
const short = 'alias' in def ? `, -${def.alias}` : ' ';
const flag = `${long}${short}`;
lines.push(` ${flag.padEnd(22)} ${def.description}`);
}
lines.push('');
process.stdout.write(lines.join('\n') + '\n');
}
/**
* Prints the current package version to stdout.
*/
export function displayVersion(): void {
process.stdout.write(`${PKG_VERSION}\n`);
}
/**
* Parses process.argv and merges with config defaults to produce
* fully resolved ArchiveOptions.
*
* Returns `null` when --help is requested (caller should display help and exit).
* Returns `'version'` when --version is requested (caller should display version and exit).
*
* @throws If required arguments are missing or values are invalid.
*/
export function parseArgs(config: AppConfig, argv?: string[]): ArchiveOptions | 'version' | null {
const raw = commandLineArgs(optionDefinitions, { argv });
// --- help ---
if (raw['help'] === true) {
return null;
}
// --- version ---
if (raw['version'] === true) {
return 'version';
}
// --- source ---
if (typeof raw['source'] !== 'string' || raw['source'].trim() === '') {
throw new Error('Missing required argument: --source (-s)');
}
// --- target ---
if (typeof raw['target'] !== 'string' || raw['target'].trim() === '') {
throw new Error('Missing required argument: --target (-t)');
}
// --- mode ---
const rawMode: string | undefined = raw['mode'] ?? config.media.mode;
const mode: ArchiveMode = (rawMode as ArchiveMode) ?? 'copy';
if (!VALID_MODES.includes(mode)) {
throw new Error(`Invalid --mode "${mode}". Must be one of: ${VALID_MODES.join(', ')}`);
}
// --- dry-run ---
const dryRun: boolean = (raw['dry-run'] as boolean | undefined) ?? config.media.dryRun ?? false;
// --- recurse ---
const recurse: boolean = (raw['recurse'] as boolean | undefined) ?? false;
// --- verbose ---
const verbose: boolean = (raw['verbose'] as boolean | undefined) ?? false;
return {
source: expandTilde(raw['source'] as string),
target: expandTilde(raw['target'] as string),
mode,
dryRun,
recurse,
verbose,
};
}