/** * Return only the user arguments (excluding runtime executable and script path), * across Node.js, Deno (run/compiled), and Bun. * * - Deno: uses Deno.args directly (already user-only in both run and compile). * - Node/Bun: uses process.execPath's basename to decide if there is a script arg. * If execPath basename is a known launcher (node/nodejs/bun/deno), skip 2; else skip 1. */ export function getUserArgs(argv?: string[]): string[] { // If argv is explicitly provided, use it instead of Deno.args // This handles test scenarios where process.argv is manually modified const useProvidedArgv = argv !== undefined; // Prefer Deno.args when available and no custom argv provided; // it's the most reliable for Deno run and compiled. // deno-lint-ignore no-explicit-any const g: any = typeof globalThis !== 'undefined' ? globalThis : {}; // Check if we should use Deno.args // Skip Deno.args if process.argv has been manipulated (test scenario detection) const processArgv = typeof process !== 'undefined' && Array.isArray(process.argv) ? process.argv : []; const argvLooksManipulated = processArgv.length > 2 && g.Deno && g.Deno.args; if (!useProvidedArgv && g.Deno && g.Deno.args && Array.isArray(g.Deno.args) && !argvLooksManipulated) { return g.Deno.args.slice(); } const a = argv ?? processArgv; if (!Array.isArray(a) || a.length === 0) return []; // Determine execPath in Node/Bun (or compat shims) let execPath = ''; if (typeof process !== 'undefined' && typeof process.execPath === 'string') { execPath = process.execPath; } else if (g.Deno && typeof g.Deno.execPath === 'function') { // Fallback for unusual shims: try Deno.execPath() if present. try { execPath = g.Deno.execPath(); } catch { /* ignore */ } } const base = basename(execPath).toLowerCase(); const knownLaunchers = new Set([ 'node', 'node.exe', 'nodejs', 'nodejs.exe', 'bun', 'bun.exe', 'deno', 'deno.exe', 'tsx', 'tsx.exe', 'ts-node', 'ts-node.exe', ]); // Always skip the executable (argv[0]). let offset = Math.min(1, a.length); // If the executable is a known runtime launcher, there's almost always a script path in argv[1]. // This handles Node, Bun, and "deno run" (but NOT "deno compile" which won't match 'deno'). if (knownLaunchers.has(base)) { offset = Math.min(2, a.length); } // Safety: if offset would skip all elements and array is not empty, don't skip anything // This handles edge cases like test environments with unusual argv setups if (offset >= a.length && a.length > 0) { offset = 0; } // Note: we intentionally avoid path/URL heuristics on argv[1] so we don't // accidentally drop the first user arg when it's a path-like value in compiled mode. return a.slice(offset); } function basename(p: string): string { if (!p) return ''; const parts = p.split(/[/\\]/); return parts[parts.length - 1] || ''; }