@radleta/just-one 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +271 -181
- package/dist/index.js +697 -28
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/lib/cli.ts","../src/lib/pid.ts","../src/lib/process.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * just-one - Ensure only one instance of a command runs at a time\n */\n\nimport { createRequire } from 'module';\nimport { parseArgs, validateOptions, getHelpText, type CliOptions } from './lib/cli.js';\nimport { readPid, writePid, deletePid, listPids, getPidFileMtime } from './lib/pid.js';\nimport {\n isProcessAlive,\n killProcess,\n waitForProcessToDie,\n spawnCommand,\n setupSignalHandlers,\n isSameProcessInstance,\n} from './lib/process.js';\n\n// Read version from package.json at runtime\nconst require = createRequire(import.meta.url);\nconst { version: VERSION } = require('../package.json');\n\nfunction log(message: string, options: CliOptions): void {\n if (!options.quiet) {\n console.log(message);\n }\n}\n\nfunction logError(message: string): void {\n console.error(message);\n}\n\nasync function handleKill(name: string, options: CliOptions): Promise<number> {\n const pid = readPid(name, options.pidDir);\n\n if (pid === null) {\n log(`No process found with name: ${name}`, options);\n return 0;\n }\n\n // Verify this is the same process we originally started (prevents killing\n // unrelated processes that reused the same PID)\n const pidFileMtime = getPidFileMtime(name, options.pidDir);\n const isSameInstance = pidFileMtime !== null && (await isSameProcessInstance(pid, pidFileMtime));\n\n if (!isSameInstance) {\n if (isProcessAlive(pid)) {\n log(`PID ${pid} belongs to a different process, not killing`, options);\n } else {\n log(`Process ${name} (PID: ${pid}) is not running, cleaning up PID file`, options);\n }\n deletePid(name, options.pidDir);\n return 0;\n }\n\n log(`Killing process ${name} (PID: ${pid})...`, options);\n const killed = killProcess(pid);\n\n if (killed) {\n await waitForProcessToDie(pid);\n deletePid(name, options.pidDir);\n log(`Process ${name} killed`, options);\n return 0;\n } else {\n logError(`Failed to kill process ${name} (PID: ${pid})`);\n return 1;\n }\n}\n\nfunction handleList(options: CliOptions): number {\n const pids = listPids(options.pidDir);\n\n if (pids.length === 0) {\n log('No tracked processes', options);\n return 0;\n }\n\n log('Tracked processes:', options);\n for (const info of pids) {\n const status = info.exists && isProcessAlive(info.pid) ? 'running' : 'stopped';\n const pidStr = info.pid > 0 ? String(info.pid) : 'unknown';\n log(` ${info.name}: PID ${pidStr} (${status})`, options);\n }\n\n return 0;\n}\n\nasync function handleRun(options: CliOptions): Promise<number> {\n const name = options.name!;\n const [command, ...args] = options.command;\n\n if (!command) {\n logError('No command specified');\n return 1;\n }\n\n // Check for existing process\n const existingPid = readPid(name, options.pidDir);\n if (existingPid !== null) {\n const pidFileMtime = getPidFileMtime(name, options.pidDir);\n const shouldKill =\n pidFileMtime !== null && (await isSameProcessInstance(existingPid, pidFileMtime));\n\n if (shouldKill) {\n log(`Killing existing process ${name} (PID: ${existingPid})...`, options);\n killProcess(existingPid);\n await waitForProcessToDie(existingPid);\n } else if (isProcessAlive(existingPid)) {\n // PID exists but doesn't match our process - likely PID reuse\n log(\n `Stale PID file detected (PID ${existingPid} belongs to different process), skipping kill`,\n options\n );\n }\n deletePid(name, options.pidDir);\n }\n\n // Spawn the new process\n log(`Starting: ${command} ${args.join(' ')}`, options);\n\n try {\n const { child, pid } = spawnCommand(command, args);\n\n // Save PID\n writePid(name, pid, options.pidDir);\n log(`Process started with PID: ${pid}`, options);\n\n // Set up signal handlers\n // Note: We intentionally do NOT delete the PID file on exit.\n // If the process exits unexpectedly, the PID file allows the next run\n // to find and kill any orphaned processes.\n setupSignalHandlers(child);\n\n // The process will keep running until it exits or is killed\n // The exit handler in setupSignalHandlers will call process.exit\n return 0;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logError(`Failed to start process: ${message}`);\n return 1;\n }\n}\n\nasync function main(): Promise<number> {\n const args = process.argv.slice(2);\n\n // Parse arguments\n const parseResult = parseArgs(args);\n if (!parseResult.success) {\n logError(`Error: ${parseResult.error}`);\n logError('Use --help for usage information');\n return 1;\n }\n\n const options = parseResult.options;\n\n // Validate options\n const validateResult = validateOptions(options);\n if (!validateResult.success) {\n logError(`Error: ${validateResult.error}`);\n logError('Use --help for usage information');\n return 1;\n }\n\n // Handle help\n if (options.help) {\n console.log(getHelpText());\n return 0;\n }\n\n // Handle version\n if (options.version) {\n console.log(`just-one v${VERSION}`);\n return 0;\n }\n\n // Handle list\n if (options.list) {\n return handleList(options);\n }\n\n // Handle kill\n if (options.kill) {\n return await handleKill(options.kill, options);\n }\n\n // Handle run\n return await handleRun(options);\n}\n\n// Run the CLI\nmain()\n .then(code => {\n // Only exit if we're not running a child process\n // The child process exit handler will call process.exit\n if (code !== 0) {\n process.exit(code);\n }\n })\n .catch(err => {\n console.error('Unexpected error:', err);\n process.exit(1);\n });\n\n// Export for testing\nexport { main };\n","/**\r\n * CLI argument parsing for just-one\r\n */\r\n\r\nexport interface CliOptions {\r\n name?: string;\r\n kill?: string;\r\n list: boolean;\r\n pidDir: string;\r\n quiet: boolean;\r\n help: boolean;\r\n version: boolean;\r\n command: string[];\r\n}\r\n\r\nexport interface ParseResult {\r\n success: true;\r\n options: CliOptions;\r\n}\r\n\r\nexport interface ParseError {\r\n success: false;\r\n error: string;\r\n}\r\n\r\nexport type ParseOutput = ParseResult | ParseError;\r\n\r\nconst DEFAULT_PID_DIR = '.just-one';\r\nconst MAX_NAME_LENGTH = 255;\r\n\r\n/**\r\n * Validate a process name for safe file operations\r\n * Rejects names containing path separators or traversal sequences\r\n */\r\nfunction isValidName(name: string): boolean {\r\n if (!name || name.length > MAX_NAME_LENGTH) {\r\n return false;\r\n }\r\n // Reject path separators and traversal sequences\r\n if (name.includes('/') || name.includes('\\\\') || name.includes('..')) {\r\n return false;\r\n }\r\n // Reject names that are only dots or whitespace\r\n if (/^[\\s.]*$/.test(name)) {\r\n return false;\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * Validate a PID directory path for safe file operations\r\n * Rejects paths containing traversal sequences\r\n */\r\nfunction isValidPidDir(dir: string): boolean {\r\n if (!dir || dir.length > 1024) {\r\n return false;\r\n }\r\n // Reject path traversal sequences\r\n if (dir.includes('..')) {\r\n return false;\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * Parse command line arguments\r\n */\r\nexport function parseArgs(args: string[]): ParseOutput {\r\n const options: CliOptions = {\r\n name: undefined,\r\n kill: undefined,\r\n list: false,\r\n pidDir: DEFAULT_PID_DIR,\r\n quiet: false,\r\n help: false,\r\n version: false,\r\n command: [],\r\n };\r\n\r\n let i = 0;\r\n while (i < args.length) {\r\n // TypeScript requires this check due to noUncheckedIndexedAccess\r\n const arg = args[i]!;\r\n\r\n // Everything after -- is the command\r\n if (arg === '--') {\r\n options.command = args.slice(i + 1);\r\n break;\r\n }\r\n\r\n // Help\r\n if (arg === '--help' || arg === '-h') {\r\n options.help = true;\r\n i++;\r\n continue;\r\n }\r\n\r\n // Version\r\n if (arg === '--version' || arg === '-v') {\r\n options.version = true;\r\n i++;\r\n continue;\r\n }\r\n\r\n // List\r\n if (arg === '--list' || arg === '-l') {\r\n options.list = true;\r\n i++;\r\n continue;\r\n }\r\n\r\n // Quiet\r\n if (arg === '--quiet' || arg === '-q') {\r\n options.quiet = true;\r\n i++;\r\n continue;\r\n }\r\n\r\n // Name (requires value)\r\n if (arg === '--name' || arg === '-n') {\r\n const value = args[i + 1];\r\n if (!value || value.startsWith('-')) {\r\n return { success: false, error: 'Option --name requires a value' };\r\n }\r\n if (!isValidName(value)) {\r\n return { success: false, error: 'Invalid name: must not contain path separators or be too long' };\r\n }\r\n options.name = value;\r\n i += 2;\r\n continue;\r\n }\r\n\r\n // Kill (requires value)\r\n if (arg === '--kill' || arg === '-k') {\r\n const value = args[i + 1];\r\n if (!value || value.startsWith('-')) {\r\n return { success: false, error: 'Option --kill requires a value' };\r\n }\r\n if (!isValidName(value)) {\r\n return { success: false, error: 'Invalid name: must not contain path separators or be too long' };\r\n }\r\n options.kill = value;\r\n i += 2;\r\n continue;\r\n }\r\n\r\n // PID directory (requires value)\r\n if (arg === '--pid-dir' || arg === '-d') {\r\n const value = args[i + 1];\r\n if (!value || value.startsWith('-')) {\r\n return { success: false, error: 'Option --pid-dir requires a value' };\r\n }\r\n if (!isValidPidDir(value)) {\r\n return { success: false, error: 'Invalid PID directory: must not contain path traversal sequences' };\r\n }\r\n options.pidDir = value;\r\n i += 2;\r\n continue;\r\n }\r\n\r\n // Unknown option\r\n if (arg.startsWith('-')) {\r\n return { success: false, error: `Unknown option: ${arg}` };\r\n }\r\n\r\n // Unexpected positional argument\r\n return { success: false, error: `Unexpected argument: ${arg}` };\r\n }\r\n\r\n return { success: true, options };\r\n}\r\n\r\n/**\r\n * Validate parsed options\r\n */\r\nexport function validateOptions(options: CliOptions): ParseOutput {\r\n // Help and version don't need validation\r\n if (options.help || options.version) {\r\n return { success: true, options };\r\n }\r\n\r\n // List doesn't need name or command\r\n if (options.list) {\r\n return { success: true, options };\r\n }\r\n\r\n // Kill only needs a name\r\n if (options.kill) {\r\n return { success: true, options };\r\n }\r\n\r\n // Running a command requires both name and command\r\n if (!options.name) {\r\n return { success: false, error: 'Option --name is required when running a command' };\r\n }\r\n\r\n if (options.command.length === 0) {\r\n return { success: false, error: 'No command specified. Use: just-one -n <name> -- <command>' };\r\n }\r\n\r\n return { success: true, options };\r\n}\r\n\r\n/**\r\n * Get help text\r\n */\r\nexport function getHelpText(): string {\r\n return `just-one - Ensure only one instance of a command runs at a time\r\n\r\nUsage:\r\n just-one -n <name> -- <command> Run command, killing any previous instance\r\n just-one -k <name> Kill a named process\r\n just-one -l List all tracked processes\r\n\r\nOptions:\r\n -n, --name <name> Name to identify this process (required for running)\r\n -k, --kill <name> Kill the named process and exit\r\n -l, --list List all tracked processes and their status\r\n -d, --pid-dir <dir> Directory for PID files (default: .just-one/)\r\n -q, --quiet Suppress output\r\n -h, --help Show this help message\r\n -v, --version Show version number\r\n\r\nExamples:\r\n # Run storybook, killing any previous instance\r\n just-one -n storybook -- npx storybook dev -p 6006\r\n\r\n # Run vite dev server\r\n just-one -n vite -- npm run dev\r\n\r\n # Kill a named process\r\n just-one -k storybook\r\n\r\n # List all tracked processes\r\n just-one -l\r\n`;\r\n}\r\n","/**\r\n * PID file operations for just-one\r\n */\r\n\r\nimport { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync, readdirSync, statSync } from 'fs';\r\nimport { join, dirname } from 'path';\r\n\r\nexport interface PidInfo {\r\n name: string;\r\n pid: number;\r\n exists: boolean;\r\n}\r\n\r\n/**\r\n * Get the path to a PID file for a given name\r\n */\r\nexport function getPidFilePath(name: string, pidDir: string): string {\r\n return join(pidDir, `${name}.pid`);\r\n}\r\n\r\n/**\r\n * Read the PID from a PID file\r\n * Returns null if the file doesn't exist or is invalid\r\n */\r\nexport function readPid(name: string, pidDir: string): number | null {\r\n const pidFile = getPidFilePath(name, pidDir);\r\n\r\n if (!existsSync(pidFile)) {\r\n return null;\r\n }\r\n\r\n try {\r\n const content = readFileSync(pidFile, 'utf8').trim();\r\n const pid = parseInt(content, 10);\r\n\r\n if (isNaN(pid) || pid <= 0) {\r\n return null;\r\n }\r\n\r\n return pid;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Write a PID to a PID file\r\n * Creates the directory if it doesn't exist\r\n */\r\nexport function writePid(name: string, pid: number, pidDir: string): void {\r\n const pidFile = getPidFilePath(name, pidDir);\r\n const dir = dirname(pidFile);\r\n\r\n if (!existsSync(dir)) {\r\n mkdirSync(dir, { recursive: true });\r\n }\r\n\r\n writeFileSync(pidFile, String(pid), 'utf8');\r\n}\r\n\r\n/**\r\n * Delete a PID file\r\n * Returns true if the file was deleted, false if it didn't exist\r\n */\r\nexport function deletePid(name: string, pidDir: string): boolean {\r\n const pidFile = getPidFilePath(name, pidDir);\r\n\r\n if (!existsSync(pidFile)) {\r\n return false;\r\n }\r\n\r\n try {\r\n unlinkSync(pidFile);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Get the modification time of a PID file as Unix timestamp (milliseconds)\r\n * Returns null if file doesn't exist\r\n */\r\nexport function getPidFileMtime(name: string, pidDir: string): number | null {\r\n const pidFile = getPidFilePath(name, pidDir);\r\n try {\r\n const stats = statSync(pidFile);\r\n return stats.mtimeMs;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * List all PID files in the directory\r\n * Returns information about each tracked process\r\n */\r\nexport function listPids(pidDir: string): PidInfo[] {\r\n if (!existsSync(pidDir)) {\r\n return [];\r\n }\r\n\r\n const files = readdirSync(pidDir);\r\n const pidFiles = files.filter(f => f.endsWith('.pid'));\r\n\r\n return pidFiles.map(file => {\r\n // Remove .pid suffix (use slice to only remove from end)\r\n const name = file.slice(0, -4);\r\n const pid = readPid(name, pidDir);\r\n\r\n return {\r\n name,\r\n pid: pid ?? 0,\r\n exists: pid !== null,\r\n };\r\n });\r\n}\r\n","/**\r\n * Cross-platform process handling for just-one\r\n */\r\n\r\nimport { spawn, execSync, ChildProcess } from 'child_process';\r\nimport pidusage from 'pidusage';\r\n\r\nconst isWindows = process.platform === 'win32';\r\n\r\n// Constants for process polling\r\nconst DEFAULT_WAIT_TIMEOUT_MS = 2000;\r\nconst CHECK_INTERVAL_MS = 100;\r\n\r\n/**\r\n * Validate that a PID is a safe positive integer for use in system calls\r\n */\r\nexport function isValidPid(pid: number): boolean {\r\n return Number.isInteger(pid) && pid > 0 && pid <= 4194304; // Max PID on most systems\r\n}\r\n\r\n// Tolerance for comparing PID file mtime with process start time\r\nconst START_TIME_TOLERANCE_MS = 5000; // 5 seconds\r\n\r\n/**\r\n * Get the start time of a process as Unix timestamp (milliseconds)\r\n * Returns null if process doesn't exist or start time can't be determined\r\n */\r\nexport async function getProcessStartTime(pid: number): Promise<number | null> {\r\n if (!isValidPid(pid)) {\r\n return null;\r\n }\r\n\r\n try {\r\n const stats = await pidusage(pid);\r\n // Calculate start time from current timestamp minus elapsed time\r\n return stats.timestamp - stats.elapsed;\r\n } catch {\r\n return null; // Process doesn't exist or can't get stats\r\n }\r\n}\r\n\r\n/**\r\n * Check if a running process is the same instance we originally spawned.\r\n * Compares process start time with PID file modification time.\r\n *\r\n * Returns true if:\r\n * - Process exists AND start time is within tolerance of pidFileMtime\r\n *\r\n * Returns false if:\r\n * - Process doesn't exist\r\n * - Can't determine process start time\r\n * - Start time doesn't match (likely PID reuse)\r\n */\r\nexport async function isSameProcessInstance(\r\n pid: number,\r\n pidFileMtimeMs: number\r\n): Promise<boolean> {\r\n const processStartTime = await getProcessStartTime(pid);\r\n if (processStartTime === null) {\r\n return false;\r\n }\r\n\r\n const diff = Math.abs(processStartTime - pidFileMtimeMs);\r\n return diff <= START_TIME_TOLERANCE_MS;\r\n}\r\n\r\n/**\r\n * Check if a process with the given PID is still running\r\n */\r\nexport function isProcessAlive(pid: number): boolean {\r\n try {\r\n if (!isValidPid(pid)) {\r\n return false;\r\n }\r\n if (isWindows) {\r\n // Windows: tasklist returns exit code 0 if process found\r\n // PID is validated as a safe integer above before interpolation\r\n const output = execSync(`tasklist /FI \"PID eq ${pid}\" /NH`, {\r\n encoding: 'utf8',\r\n stdio: ['pipe', 'pipe', 'pipe'],\r\n });\r\n return output.includes(String(pid));\r\n } else {\r\n // Unix/Mac: kill -0 checks if process exists without killing it\r\n process.kill(pid, 0);\r\n return true;\r\n }\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Kill a process by PID\r\n * Returns true if the process was killed, false if it wasn't running\r\n */\r\nexport function killProcess(pid: number): boolean {\r\n if (!isValidPid(pid) || !isProcessAlive(pid)) {\r\n return false;\r\n }\r\n\r\n try {\r\n if (isWindows) {\r\n // Windows: taskkill with /T kills the process tree, /F forces\r\n // PID is validated as a safe integer above before interpolation\r\n execSync(`taskkill /PID ${pid} /T /F`, {\r\n stdio: ['pipe', 'pipe', 'pipe'],\r\n });\r\n } else {\r\n // Unix: try to kill process group first (catches child processes),\r\n // fall back to killing just the process if group kill fails\r\n const killed = tryKillUnix(-pid) || tryKillUnix(pid);\r\n if (!killed) {\r\n return false;\r\n }\r\n }\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Helper to attempt Unix kill with error handling\r\n */\r\nfunction tryKillUnix(pid: number): boolean {\r\n try {\r\n process.kill(pid, 'SIGTERM');\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Wait for a process to die, with timeout\r\n * @param pid - Process ID to wait for\r\n * @param timeoutMs - Maximum time to wait (default: 2000ms)\r\n */\r\nexport async function waitForProcessToDie(\r\n pid: number,\r\n timeoutMs: number = DEFAULT_WAIT_TIMEOUT_MS\r\n): Promise<boolean> {\r\n const startTime = Date.now();\r\n\r\n while (Date.now() - startTime < timeoutMs) {\r\n if (!isProcessAlive(pid)) {\r\n return true;\r\n }\r\n await new Promise(resolve => setTimeout(resolve, CHECK_INTERVAL_MS));\r\n }\r\n\r\n return !isProcessAlive(pid);\r\n}\r\n\r\nexport interface SpawnResult {\r\n child: ChildProcess;\r\n pid: number;\r\n}\r\n\r\n/**\r\n * Spawn a command with stdio forwarding\r\n */\r\nexport function spawnCommand(command: string, args: string[]): SpawnResult {\r\n // On Windows, pass entire command as a single string to avoid escaping issues\r\n // with shell: true (DEP0190 warning and argument handling)\r\n const spawnCmd = isWindows ? `${command} ${args.join(' ')}` : command;\r\n const spawnArgs = isWindows ? [] : args;\r\n\r\n const child = spawn(spawnCmd, spawnArgs, {\r\n stdio: 'inherit',\r\n shell: isWindows,\r\n detached: !isWindows,\r\n });\r\n\r\n if (child.pid === undefined) {\r\n throw new Error('Failed to spawn process');\r\n }\r\n\r\n return {\r\n child,\r\n pid: child.pid,\r\n };\r\n}\r\n\r\n/**\r\n * Set up signal handlers to forward signals to child process\r\n * Note: Both SIGINT and SIGTERM are forwarded as SIGTERM to ensure\r\n * consistent graceful shutdown behavior across different termination methods.\r\n */\r\nexport function setupSignalHandlers(child: ChildProcess, onExit?: () => void): void {\r\n const handleSignal = (_signal: NodeJS.Signals) => {\r\n if (child.pid && isValidPid(child.pid)) {\r\n if (isWindows) {\r\n try {\r\n // PID is validated as a safe integer above before interpolation\r\n execSync(`taskkill /PID ${child.pid} /T /F`, {\r\n stdio: ['pipe', 'pipe', 'pipe'],\r\n });\r\n } catch {\r\n // Process might already be dead\r\n }\r\n } else {\r\n // Forward as SIGTERM for graceful shutdown\r\n child.kill('SIGTERM');\r\n }\r\n }\r\n };\r\n\r\n // Forward both SIGINT (Ctrl+C) and SIGTERM to child as SIGTERM\r\n process.on('SIGINT', () => handleSignal('SIGINT'));\r\n process.on('SIGTERM', () => handleSignal('SIGTERM'));\r\n\r\n child.on('exit', (code, signal) => {\r\n if (onExit) {\r\n onExit();\r\n }\r\n if (signal) {\r\n process.exit(128 + (signal === 'SIGTERM' ? 15 : signal === 'SIGINT' ? 2 : 1));\r\n }\r\n process.exit(code ?? 0);\r\n });\r\n\r\n child.on('error', err => {\r\n console.error(`Failed to start process: ${err.message}`);\r\n process.exit(1);\r\n });\r\n}\r\n"],"mappings":"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","names":["require"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/lib/cli.ts","../src/lib/pid.ts","../src/lib/process.ts","../src/lib/log.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * just-one - Ensure only one instance of a command runs at a time\n */\n\nimport { createRequire } from 'module';\nimport { parseArgs, validateOptions, getHelpText, type CliOptions } from './lib/cli.js';\nimport { readPid, writePid, deletePid, listPids, getPidFileMtime } from './lib/pid.js';\nimport {\n isProcessAlive,\n terminateProcess,\n spawnCommand,\n spawnCommandDaemon,\n setupSignalHandlers,\n isSameProcessInstance,\n} from './lib/process.js';\nimport { existsSync } from 'fs';\nimport {\n getLogFilePath,\n rotateLogIfNeeded,\n readLogLines,\n tailLogFile,\n deleteLogFiles,\n} from './lib/log.js';\n\n// Read version from package.json at runtime\nconst require = createRequire(import.meta.url);\nconst { version: VERSION } = require('../package.json');\n\nfunction log(message: string, options: CliOptions): void {\n if (!options.quiet) {\n console.log(message);\n }\n}\n\nfunction logError(message: string): void {\n console.error(message);\n}\n\nasync function handleKill(name: string, options: CliOptions): Promise<number> {\n const pid = readPid(name, options.pidDir);\n\n if (pid === null) {\n log(`No process found with name: ${name}`, options);\n return 0;\n }\n\n // Verify this is the same process we originally started (prevents killing\n // unrelated processes that reused the same PID)\n const pidFileMtime = getPidFileMtime(name, options.pidDir);\n const isSameInstance = pidFileMtime !== null && (await isSameProcessInstance(pid, pidFileMtime));\n\n if (!isSameInstance) {\n if (isProcessAlive(pid)) {\n log(`PID ${pid} belongs to a different process, not killing`, options);\n } else {\n log(`Process ${name} (PID: ${pid}) is not running, cleaning up PID file`, options);\n }\n deletePid(name, options.pidDir);\n return 0;\n }\n\n log(`Killing process ${name} (PID: ${pid})...`, options);\n const graceMs = options.grace !== undefined ? options.grace * 1000 : undefined;\n const terminated = await terminateProcess(pid, graceMs);\n\n if (terminated) {\n deletePid(name, options.pidDir);\n log(`Process ${name} killed`, options);\n return 0;\n } else {\n logError(`Failed to kill process ${name} (PID: ${pid})`);\n return 1;\n }\n}\n\nfunction handleList(options: CliOptions): number {\n const pids = listPids(options.pidDir);\n\n if (pids.length === 0) {\n log('No tracked processes', options);\n return 0;\n }\n\n log('Tracked processes:', options);\n for (const info of pids) {\n const status = info.exists && isProcessAlive(info.pid) ? 'running' : 'stopped';\n const pidStr = info.pid > 0 ? String(info.pid) : 'unknown';\n log(` ${info.name}: PID ${pidStr} (${status})`, options);\n }\n\n return 0;\n}\n\nasync function handleRun(options: CliOptions): Promise<number> {\n const name = options.name!;\n const [command, ...args] = options.command;\n\n if (!command) {\n logError('No command specified');\n return 1;\n }\n\n // Check for existing process\n const existingPid = readPid(name, options.pidDir);\n if (existingPid !== null) {\n const pidFileMtime = getPidFileMtime(name, options.pidDir);\n const shouldKill =\n pidFileMtime !== null && (await isSameProcessInstance(existingPid, pidFileMtime));\n\n if (shouldKill) {\n // In ensure mode, if the process is verified running, skip restart\n if (options.ensure) {\n log(`Process ${name} is already running (PID: ${existingPid}), skipping`, options);\n return 0;\n }\n log(`Killing existing process ${name} (PID: ${existingPid})...`, options);\n const graceMs = options.grace !== undefined ? options.grace * 1000 : undefined;\n const terminated = await terminateProcess(existingPid, graceMs);\n if (!terminated) {\n logError(`Warning: process ${existingPid} may still be running`);\n }\n } else if (isProcessAlive(existingPid)) {\n // PID exists but doesn't match our process - likely PID reuse\n log(\n `Stale PID file detected (PID ${existingPid} belongs to different process), skipping kill`,\n options\n );\n }\n deletePid(name, options.pidDir);\n }\n\n // Spawn the new process\n log(`Starting: ${command} ${args.join(' ')}`, options);\n\n try {\n if (options.daemon) {\n // Daemon mode: run detached with log file capture\n rotateLogIfNeeded(name, options.pidDir);\n const logPath = getLogFilePath(name, options.pidDir);\n const { pid } = spawnCommandDaemon(command, args, logPath);\n\n writePid(name, pid, options.pidDir);\n log(`Daemon started with PID: ${pid}`, options);\n log(`Logs: ${logPath}`, options);\n return 0;\n }\n\n // Foreground mode (existing behavior)\n const { child, pid } = spawnCommand(command, args);\n\n // Save PID\n writePid(name, pid, options.pidDir);\n log(`Process started with PID: ${pid}`, options);\n\n // Set up signal handlers\n // Note: We intentionally do NOT delete the PID file on exit.\n // If the process exits unexpectedly, the PID file allows the next run\n // to find and kill any orphaned processes.\n setupSignalHandlers(child);\n\n // The process will keep running until it exits or is killed\n // The exit handler in setupSignalHandlers will call process.exit\n return 0;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logError(`Failed to start process: ${message}`);\n return 1;\n }\n}\n\nasync function handleStatus(name: string, options: CliOptions): Promise<number> {\n const pid = readPid(name, options.pidDir);\n\n if (pid === null) {\n log(`Process ${name}: not tracked`, options);\n return 1;\n }\n\n const pidFileMtime = getPidFileMtime(name, options.pidDir);\n const isSameInstance = pidFileMtime !== null && (await isSameProcessInstance(pid, pidFileMtime));\n\n if (isSameInstance) {\n log(`Process ${name}: running (PID ${pid})`, options);\n return 0;\n }\n\n if (isProcessAlive(pid)) {\n log(`Process ${name}: stopped (PID ${pid} belongs to a different process)`, options);\n } else {\n log(`Process ${name}: stopped`, options);\n }\n return 1;\n}\n\nasync function handleKillAll(options: CliOptions): Promise<number> {\n const pids = listPids(options.pidDir);\n\n if (pids.length === 0) {\n log('No tracked processes', options);\n return 0;\n }\n\n let failed = false;\n for (const info of pids) {\n if (!info.exists || info.pid <= 0) {\n deletePid(info.name, options.pidDir);\n continue;\n }\n\n const pidFileMtime = getPidFileMtime(info.name, options.pidDir);\n const isSameInstance =\n pidFileMtime !== null && (await isSameProcessInstance(info.pid, pidFileMtime));\n\n if (!isSameInstance) {\n log(`Process ${info.name} (PID: ${info.pid}) is stale, cleaning up`, options);\n deletePid(info.name, options.pidDir);\n continue;\n }\n\n log(`Killing process ${info.name} (PID: ${info.pid})...`, options);\n const graceMs = options.grace !== undefined ? options.grace * 1000 : undefined;\n const terminated = await terminateProcess(info.pid, graceMs);\n\n if (terminated) {\n deletePid(info.name, options.pidDir);\n log(`Process ${info.name} killed`, options);\n } else {\n logError(`Failed to kill process ${info.name} (PID: ${info.pid})`);\n failed = true;\n }\n }\n\n return failed ? 1 : 0;\n}\n\nasync function handleClean(options: CliOptions): Promise<number> {\n const pids = listPids(options.pidDir);\n\n if (pids.length === 0) {\n log('No PID files to clean', options);\n return 0;\n }\n\n let cleaned = 0;\n for (const info of pids) {\n if (!info.exists || info.pid <= 0) {\n deletePid(info.name, options.pidDir);\n deleteLogFiles(info.name, options.pidDir);\n cleaned++;\n continue;\n }\n\n const pidFileMtime = getPidFileMtime(info.name, options.pidDir);\n const isSameInstance =\n pidFileMtime !== null && (await isSameProcessInstance(info.pid, pidFileMtime));\n\n if (!isSameInstance) {\n log(`Removing stale PID file: ${info.name} (PID: ${info.pid})`, options);\n deletePid(info.name, options.pidDir);\n deleteLogFiles(info.name, options.pidDir);\n cleaned++;\n }\n }\n\n if (cleaned === 0) {\n log('No stale PID files found', options);\n } else {\n log(`Cleaned ${cleaned} stale PID file${cleaned === 1 ? '' : 's'}`, options);\n }\n\n return 0;\n}\n\nasync function handlePid(name: string, options: CliOptions): Promise<number> {\n const pid = readPid(name, options.pidDir);\n\n if (pid === null) {\n log(`No process found with name: ${name}`, options);\n return 1;\n }\n\n const pidFileMtime = getPidFileMtime(name, options.pidDir);\n const isSameInstance = pidFileMtime !== null && (await isSameProcessInstance(pid, pidFileMtime));\n\n if (isSameInstance) {\n log(String(pid), options);\n return 0;\n }\n\n log(`Process ${name} is not running`, options);\n return 1;\n}\n\nasync function handleWait(name: string, options: CliOptions): Promise<number> {\n const pid = readPid(name, options.pidDir);\n\n if (pid === null) {\n log(`No process found with name: ${name}`, options);\n return 1;\n }\n\n // Check if process is alive first, then verify identity if possible.\n // Wait is non-destructive (we only poll), so we can be lenient with identity checks.\n if (!isProcessAlive(pid)) {\n log(`Process ${name} (PID: ${pid}) is not running`, options);\n return 1;\n }\n\n log(`Waiting for process ${name} (PID: ${pid}) to exit...`, options);\n\n const timeoutMs = options.timeout !== undefined ? options.timeout * 1000 : undefined;\n const startTime = Date.now();\n const pollInterval = 500;\n\n while (isProcessAlive(pid)) {\n if (timeoutMs !== undefined && Date.now() - startTime >= timeoutMs) {\n log(`Timeout waiting for process ${name} (PID: ${pid})`, options);\n return 1;\n }\n await new Promise(resolve => setTimeout(resolve, pollInterval));\n }\n\n log(`Process ${name} (PID: ${pid}) has exited`, options);\n return 0;\n}\n\nasync function handleLogs(name: string, options: CliOptions): Promise<number> {\n const logPath = getLogFilePath(name, options.pidDir);\n\n if (!existsSync(logPath)) {\n logError(`No logs found for process: ${name}`);\n return 1;\n }\n\n if (!options.tail) {\n // Static mode: print lines and exit\n const lines = readLogLines(name, options.pidDir, options.lines);\n for (const line of lines) {\n console.log(line);\n }\n return 0;\n }\n\n // Follow mode: print initial lines, then tail\n const initialLines = options.lines ?? 10;\n const initial = readLogLines(name, options.pidDir, initialLines);\n for (const line of initial) {\n console.log(line);\n }\n\n const handle = tailLogFile(name, options.pidDir, {\n onLine: line => console.log(line),\n pollIntervalMs: 500,\n });\n\n // Poll PID to auto-stop when process dies\n const pid = readPid(name, options.pidDir);\n const pidPollInterval = setInterval(() => {\n if (pid !== null && !isProcessAlive(pid)) {\n handle.stop();\n clearInterval(pidPollInterval);\n log(`Process ${name} has exited`, options);\n process.exit(0);\n }\n // Also stop if no PID file at all\n const currentPid = readPid(name, options.pidDir);\n if (currentPid === null) {\n handle.stop();\n clearInterval(pidPollInterval);\n log(`Process ${name} is no longer tracked`, options);\n process.exit(0);\n }\n }, 1000);\n\n // Handle SIGINT/SIGTERM for clean exit\n const cleanup = () => {\n handle.stop();\n clearInterval(pidPollInterval);\n process.exit(0);\n };\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n\n // Keep the process alive\n return new Promise<number>(() => {\n // Never resolves — exits via cleanup or process death detection\n });\n}\n\nasync function main(): Promise<number> {\n const args = process.argv.slice(2);\n\n // Parse arguments\n const parseResult = parseArgs(args);\n if (!parseResult.success) {\n logError(`Error: ${parseResult.error}`);\n logError('Use --help for usage information');\n return 1;\n }\n\n const options = parseResult.options;\n\n // Validate options\n const validateResult = validateOptions(options);\n if (!validateResult.success) {\n logError(`Error: ${validateResult.error}`);\n logError('Use --help for usage information');\n return 1;\n }\n\n // Handle help\n if (options.help) {\n console.log(getHelpText());\n return 0;\n }\n\n // Handle version\n if (options.version) {\n console.log(`just-one v${VERSION}`);\n return 0;\n }\n\n // Handle list\n if (options.list) {\n return handleList(options);\n }\n\n // Handle kill\n if (options.kill) {\n return await handleKill(options.kill, options);\n }\n\n // Handle kill all\n if (options.killAll) {\n return await handleKillAll(options);\n }\n\n // Handle status\n if (options.status) {\n return await handleStatus(options.status, options);\n }\n\n // Handle logs\n if (options.logs) {\n return await handleLogs(options.logs, options);\n }\n\n // Handle clean\n if (options.clean) {\n return await handleClean(options);\n }\n\n // Handle pid\n if (options.pid) {\n return await handlePid(options.pid, options);\n }\n\n // Handle wait\n if (options.wait) {\n return await handleWait(options.wait, options);\n }\n\n // Handle run (with optional --ensure modifier)\n return await handleRun(options);\n}\n\n// Run the CLI\nmain()\n .then(code => {\n // Only exit if we're not running a child process\n // The child process exit handler will call process.exit\n if (code !== 0) {\n process.exit(code);\n }\n })\n .catch(err => {\n console.error('Unexpected error:', err);\n process.exit(1);\n });\n\n// Export for testing\nexport { main };\n","/**\n * CLI argument parsing for just-one\n */\n\nexport interface CliOptions {\n name?: string;\n kill?: string;\n list: boolean;\n status?: string;\n killAll: boolean;\n ensure: boolean;\n clean: boolean;\n pid?: string;\n wait?: string;\n timeout?: number;\n grace?: number;\n daemon: boolean;\n logs?: string;\n tail: boolean;\n lines?: number;\n pidDir: string;\n quiet: boolean;\n help: boolean;\n version: boolean;\n command: string[];\n}\n\nexport interface ParseResult {\n success: true;\n options: CliOptions;\n}\n\nexport interface ParseError {\n success: false;\n error: string;\n}\n\nexport type ParseOutput = ParseResult | ParseError;\n\nconst DEFAULT_PID_DIR = '.just-one';\nconst MAX_NAME_LENGTH = 255;\n\n/**\n * Validate a process name for safe file operations\n * Rejects names containing path separators or traversal sequences\n */\nfunction isValidName(name: string): boolean {\n if (!name || name.length > MAX_NAME_LENGTH) {\n return false;\n }\n // Reject path separators and traversal sequences\n if (name.includes('/') || name.includes('\\\\') || name.includes('..')) {\n return false;\n }\n // Reject names that are only dots or whitespace\n if (/^[\\s.]*$/.test(name)) {\n return false;\n }\n return true;\n}\n\n/**\n * Validate a PID directory path for safe file operations\n * Rejects paths containing traversal sequences\n */\nfunction isValidPidDir(dir: string): boolean {\n if (!dir || dir.length > 1024) {\n return false;\n }\n // Reject path traversal sequences\n if (dir.includes('..')) {\n return false;\n }\n return true;\n}\n\n/**\n * Parse command line arguments\n */\nexport function parseArgs(args: string[]): ParseOutput {\n const options: CliOptions = {\n name: undefined,\n kill: undefined,\n list: false,\n status: undefined,\n killAll: false,\n ensure: false,\n clean: false,\n pid: undefined,\n wait: undefined,\n timeout: undefined,\n grace: undefined,\n daemon: false,\n logs: undefined,\n tail: false,\n lines: undefined,\n pidDir: DEFAULT_PID_DIR,\n quiet: false,\n help: false,\n version: false,\n command: [],\n };\n\n let i = 0;\n while (i < args.length) {\n // TypeScript requires this check due to noUncheckedIndexedAccess\n const arg = args[i]!;\n\n // Everything after -- is the command\n if (arg === '--') {\n options.command = args.slice(i + 1);\n break;\n }\n\n // Help\n if (arg === '--help' || arg === '-h') {\n options.help = true;\n i++;\n continue;\n }\n\n // Version\n if (arg === '--version' || arg === '-v') {\n options.version = true;\n i++;\n continue;\n }\n\n // List\n if (arg === '--list' || arg === '-l') {\n options.list = true;\n i++;\n continue;\n }\n\n // Quiet\n if (arg === '--quiet' || arg === '-q') {\n options.quiet = true;\n i++;\n continue;\n }\n\n // Name (requires value)\n if (arg === '--name' || arg === '-n') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --name requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.name = value;\n i += 2;\n continue;\n }\n\n // Kill (requires value)\n if (arg === '--kill' || arg === '-k') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --kill requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.kill = value;\n i += 2;\n continue;\n }\n\n // PID directory (requires value)\n if (arg === '--pid-dir' || arg === '-d') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --pid-dir requires a value' };\n }\n if (!isValidPidDir(value)) {\n return {\n success: false,\n error: 'Invalid PID directory: must not contain path traversal sequences',\n };\n }\n options.pidDir = value;\n i += 2;\n continue;\n }\n\n // Status (requires value)\n if (arg === '--status' || arg === '-s') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --status requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.status = value;\n i += 2;\n continue;\n }\n\n // Kill All\n if (arg === '--kill-all' || arg === '-K') {\n options.killAll = true;\n i++;\n continue;\n }\n\n // Ensure\n if (arg === '--ensure' || arg === '-e') {\n options.ensure = true;\n i++;\n continue;\n }\n\n // Clean\n if (arg === '--clean') {\n options.clean = true;\n i++;\n continue;\n }\n\n // PID output (requires value)\n if (arg === '--pid' || arg === '-p') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --pid requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.pid = value;\n i += 2;\n continue;\n }\n\n // Wait (requires value)\n if (arg === '--wait' || arg === '-w') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --wait requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.wait = value;\n i += 2;\n continue;\n }\n\n // Daemon\n if (arg === '--daemon' || arg === '-D') {\n options.daemon = true;\n i++;\n continue;\n }\n\n // Logs (requires value)\n if (arg === '--logs' || arg === '-L') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --logs requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.logs = value;\n i += 2;\n continue;\n }\n\n // Tail (follow logs)\n if (arg === '--tail' || arg === '-f') {\n options.tail = true;\n i++;\n continue;\n }\n\n // Lines (requires positive integer value)\n if (arg === '--lines') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --lines requires a positive integer' };\n }\n const num = Number(value);\n if (!Number.isInteger(num) || num <= 0) {\n return { success: false, error: 'Option --lines requires a positive integer' };\n }\n options.lines = num;\n i += 2;\n continue;\n }\n\n // Timeout (requires numeric value)\n if (arg === '--timeout' || arg === '-t') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --timeout requires a positive number' };\n }\n const num = Number(value);\n if (isNaN(num) || num <= 0) {\n return { success: false, error: 'Option --timeout requires a positive number' };\n }\n options.timeout = num;\n i += 2;\n continue;\n }\n\n // Grace period for kill (requires numeric value)\n if (arg === '--grace' || arg === '-g') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --grace requires a positive number (seconds)' };\n }\n const num = Number(value);\n if (isNaN(num) || num <= 0) {\n return { success: false, error: 'Option --grace requires a positive number (seconds)' };\n }\n options.grace = num;\n i += 2;\n continue;\n }\n\n // Unknown option\n if (arg.startsWith('-')) {\n return { success: false, error: `Unknown option: ${arg}` };\n }\n\n // Unexpected positional argument\n return { success: false, error: `Unexpected argument: ${arg}` };\n }\n\n return { success: true, options };\n}\n\n/**\n * Validate parsed options\n */\nexport function validateOptions(options: CliOptions): ParseOutput {\n // Help and version don't need validation\n if (options.help || options.version) {\n return { success: true, options };\n }\n\n // List doesn't need name or command\n if (options.list) {\n return { success: true, options };\n }\n\n // Kill only needs a name\n if (options.kill) {\n return { success: true, options };\n }\n\n // Logs is a standalone operation\n if (options.logs) {\n return { success: true, options };\n }\n\n // --tail and --lines only valid with --logs\n if (options.tail) {\n return { success: false, error: 'Option --tail can only be used with --logs' };\n }\n if (options.lines !== undefined) {\n return { success: false, error: 'Option --lines can only be used with --logs' };\n }\n\n // Standalone operations that don't need name or command\n if (options.status) {\n return { success: true, options };\n }\n if (options.killAll) {\n return { success: true, options };\n }\n if (options.clean) {\n return { success: true, options };\n }\n if (options.pid) {\n return { success: true, options };\n }\n if (options.wait) {\n if (options.timeout !== undefined && options.timeout <= 0) {\n return { success: false, error: 'Option --timeout requires a positive number' };\n }\n return { success: true, options };\n }\n\n // Timeout without wait is an error\n if (options.timeout !== undefined && !options.wait) {\n return { success: false, error: 'Option --timeout can only be used with --wait' };\n }\n\n // Daemon requires name and command (validated below as part of normal run)\n // No special check needed here since daemon is a modifier for run\n\n // Running a command requires both name and command\n if (!options.name) {\n return { success: false, error: 'Option --name is required when running a command' };\n }\n\n if (options.command.length === 0) {\n return { success: false, error: 'No command specified. Use: just-one -n <name> -- <command>' };\n }\n\n return { success: true, options };\n}\n\n/**\n * Get help text\n */\nexport function getHelpText(): string {\n return `just-one - Ensure only one instance of a command runs at a time\n\nUsage:\n just-one -n <name> -- <command> Run command, killing any previous instance\n just-one -n <name> -e -- <command> Run only if not already running (ensure mode)\n just-one -n <name> -D -- <command> Run in daemon mode (background, logs to file)\n just-one -L <name> View captured logs for a named process\n just-one -L <name> -f Follow logs in real-time (auto-exits on process death)\n just-one -k <name> Kill a named process\n just-one -K Kill all tracked processes\n just-one -s <name> Check if a named process is running\n just-one -p <name> Print the PID of a named process\n just-one -w <name> Wait for a named process to exit\n just-one -l List all tracked processes\n just-one --clean Remove stale PID files and orphaned log files\n\nOptions:\n -n, --name <name> Name to identify this process (required for running)\n -D, --daemon Run in background with output captured to log file\n -L, --logs <name> View captured logs for a named process\n -f, --tail Follow log output in real-time (use with --logs)\n --lines <n> Number of lines to show (use with --logs, default: all)\n -k, --kill <name> Kill the named process and exit\n -K, --kill-all Kill all tracked processes\n -s, --status <name> Check if a named process is running (exit 0=running, 1=stopped)\n -e, --ensure Only start if not already running (use with -n and command)\n -p, --pid <name> Print the PID of a named process\n -w, --wait <name> Wait for a named process to exit\n -t, --timeout <secs> Timeout in seconds (use with --wait)\n -g, --grace <secs> Grace period before force kill (default: 5s)\n --clean Remove stale PID files and orphaned log files\n -l, --list List all tracked processes and their status\n -d, --pid-dir <dir> Directory for PID files (default: .just-one/)\n -q, --quiet Suppress output\n -h, --help Show this help message\n -v, --version Show version number\n\nExamples:\n # Run storybook, killing any previous instance\n just-one -n storybook -- npx storybook dev -p 6006\n\n # Run vite dev server only if not already running\n just-one -n vite -e -- npm run dev\n\n # Run in daemon mode (background with log capture)\n just-one -n myapp -D -- npm start\n\n # View captured logs\n just-one -L myapp\n\n # View last 50 lines of logs\n just-one -L myapp --lines 50\n\n # Follow logs in real-time (like tail -f)\n just-one -L myapp -f\n\n # Check if a process is running\n just-one -s storybook\n\n # Get the PID for scripting\n pid=$(just-one -p storybook -q)\n\n # Kill all tracked processes\n just-one -K\n\n # Wait for a process to exit (with 30s timeout)\n just-one -w myapp -t 30\n\n # Clean up stale PID files and orphaned logs\n just-one --clean\n\n # Kill a named process\n just-one -k storybook\n\n # List all tracked processes\n just-one -l\n`;\n}\n","/**\n * PID file operations for just-one\n */\n\nimport {\n readFileSync,\n writeFileSync,\n unlinkSync,\n existsSync,\n mkdirSync,\n readdirSync,\n statSync,\n} from 'fs';\nimport { join, dirname } from 'path';\n\nexport interface PidInfo {\n name: string;\n pid: number;\n exists: boolean;\n}\n\n/**\n * Get the path to a PID file for a given name\n */\nexport function getPidFilePath(name: string, pidDir: string): string {\n return join(pidDir, `${name}.pid`);\n}\n\n/**\n * Read the PID from a PID file\n * Returns null if the file doesn't exist or is invalid\n */\nexport function readPid(name: string, pidDir: string): number | null {\n const pidFile = getPidFilePath(name, pidDir);\n\n if (!existsSync(pidFile)) {\n return null;\n }\n\n try {\n const content = readFileSync(pidFile, 'utf8').trim();\n const pid = parseInt(content, 10);\n\n if (isNaN(pid) || pid <= 0) {\n return null;\n }\n\n return pid;\n } catch {\n return null;\n }\n}\n\n/**\n * Write a PID to a PID file\n * Creates the directory if it doesn't exist\n */\nexport function writePid(name: string, pid: number, pidDir: string): void {\n const pidFile = getPidFilePath(name, pidDir);\n const dir = dirname(pidFile);\n\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(pidFile, String(pid), 'utf8');\n}\n\n/**\n * Delete a PID file\n * Returns true if the file was deleted, false if it didn't exist\n */\nexport function deletePid(name: string, pidDir: string): boolean {\n const pidFile = getPidFilePath(name, pidDir);\n\n if (!existsSync(pidFile)) {\n return false;\n }\n\n try {\n unlinkSync(pidFile);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get the modification time of a PID file as Unix timestamp (milliseconds)\n * Returns null if file doesn't exist\n */\nexport function getPidFileMtime(name: string, pidDir: string): number | null {\n const pidFile = getPidFilePath(name, pidDir);\n try {\n const stats = statSync(pidFile);\n return stats.mtimeMs;\n } catch {\n return null;\n }\n}\n\n/**\n * List all PID files in the directory\n * Returns information about each tracked process\n */\nexport function listPids(pidDir: string): PidInfo[] {\n if (!existsSync(pidDir)) {\n return [];\n }\n\n const files = readdirSync(pidDir);\n const pidFiles = files.filter(f => f.endsWith('.pid'));\n\n return pidFiles.map(file => {\n // Remove .pid suffix (use slice to only remove from end)\n const name = file.slice(0, -4);\n const pid = readPid(name, pidDir);\n\n return {\n name,\n pid: pid ?? 0,\n exists: pid !== null,\n };\n });\n}\n","/**\n * Cross-platform process handling for just-one\n */\n\nimport { spawn, execSync, ChildProcess, type StdioOptions } from 'child_process';\nimport { openSync, closeSync } from 'fs';\nimport pidusage from 'pidusage';\n\nconst isWindows = process.platform === 'win32';\n\n// Constants for process termination\nconst DEFAULT_GRACE_PERIOD_MS = 5000; // How long to wait after SIGTERM before escalating\nconst FORCE_KILL_WAIT_MS = 2000; // How long to wait after SIGKILL for process to die\nconst CHECK_INTERVAL_MS = 100;\n\n/**\n * Validate that a PID is a safe positive integer for use in system calls\n */\nexport function isValidPid(pid: number): boolean {\n return Number.isInteger(pid) && pid > 0 && pid <= 4194304; // Max PID on most systems\n}\n\n// Tolerance for comparing PID file mtime with process start time\nconst START_TIME_TOLERANCE_MS = 5000; // 5 seconds\n\n/**\n * Get the start time of a process as Unix timestamp (milliseconds)\n * Returns null if process doesn't exist or start time can't be determined\n */\nexport async function getProcessStartTime(pid: number): Promise<number | null> {\n if (!isValidPid(pid)) {\n return null;\n }\n\n try {\n const stats = await pidusage(pid);\n // Calculate start time from current timestamp minus elapsed time\n return stats.timestamp - stats.elapsed;\n } catch {\n return null; // Process doesn't exist or can't get stats\n }\n}\n\n/**\n * Check if a running process is the same instance we originally spawned.\n * Compares process start time with PID file modification time.\n *\n * Returns true if:\n * - Process exists AND start time is within tolerance of pidFileMtime\n *\n * Returns false if:\n * - Process doesn't exist\n * - Can't determine process start time\n * - Start time doesn't match (likely PID reuse)\n */\nexport async function isSameProcessInstance(pid: number, pidFileMtimeMs: number): Promise<boolean> {\n const processStartTime = await getProcessStartTime(pid);\n if (processStartTime === null) {\n return false;\n }\n\n const diff = Math.abs(processStartTime - pidFileMtimeMs);\n return diff <= START_TIME_TOLERANCE_MS;\n}\n\n/**\n * Check if a process with the given PID is still running\n */\nexport function isProcessAlive(pid: number): boolean {\n try {\n if (!isValidPid(pid)) {\n return false;\n }\n if (isWindows) {\n // Windows: tasklist returns exit code 0 if process found\n // PID is validated as a safe integer above before interpolation\n const output = execSync(`tasklist /FI \"PID eq ${pid}\" /NH`, {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return output.includes(String(pid));\n } else {\n // Unix/Mac: kill -0 checks if process exists without killing it\n process.kill(pid, 0);\n return true;\n }\n } catch {\n return false;\n }\n}\n\n/**\n * Kill a process by PID\n * Returns true if the process was killed, false if it wasn't running\n */\nexport function killProcess(pid: number): boolean {\n if (!isValidPid(pid) || !isProcessAlive(pid)) {\n return false;\n }\n\n try {\n if (isWindows) {\n // Windows: taskkill with /T kills the process tree, /F forces\n // PID is validated as a safe integer above before interpolation\n execSync(`taskkill /PID ${pid} /T /F`, {\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } else {\n // Unix: try to kill process group first (catches child processes),\n // fall back to killing just the process if group kill fails\n const killed = tryKillUnix(-pid) || tryKillUnix(pid);\n if (!killed) {\n return false;\n }\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Helper to attempt Unix kill with error handling\n */\nfunction tryKillUnix(pid: number): boolean {\n try {\n process.kill(pid, 'SIGTERM');\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Wait for a process to die, with timeout\n * @param pid - Process ID to wait for\n * @param timeoutMs - Maximum time to wait (default: 5000ms)\n */\nexport async function waitForProcessToDie(\n pid: number,\n timeoutMs: number = DEFAULT_GRACE_PERIOD_MS\n): Promise<boolean> {\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeoutMs) {\n if (!isProcessAlive(pid)) {\n return true;\n }\n await new Promise(resolve => setTimeout(resolve, CHECK_INTERVAL_MS));\n }\n\n return !isProcessAlive(pid);\n}\n\n/**\n * Force kill a process by PID using SIGKILL (Unix) or taskkill /F (Windows).\n * This is a last resort after SIGTERM fails.\n */\nexport function forceKillProcess(pid: number): boolean {\n if (!isValidPid(pid) || !isProcessAlive(pid)) {\n return false;\n }\n\n try {\n if (isWindows) {\n // PID is validated as a safe integer above before interpolation\n execSync(`taskkill /PID ${pid} /T /F`, {\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } else {\n // Try process group first, then individual PID\n let killed = false;\n try {\n process.kill(-pid, 'SIGKILL');\n killed = true;\n } catch {\n /* group kill may fail */\n }\n try {\n process.kill(pid, 'SIGKILL');\n killed = true;\n } catch {\n /* individual kill may fail */\n }\n if (!killed) return false;\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Terminate a process with graceful shutdown and SIGKILL escalation.\n *\n * Flow: SIGTERM → wait grace period → SIGKILL → wait 2s → give up\n *\n * @param pid - Process ID to terminate\n * @param gracePeriodMs - How long to wait after SIGTERM before escalating (default: 5000ms)\n * @returns true if process is dead, false if it could not be killed\n */\nexport async function terminateProcess(pid: number, gracePeriodMs?: number): Promise<boolean> {\n const grace = gracePeriodMs ?? DEFAULT_GRACE_PERIOD_MS;\n\n if (!isValidPid(pid)) {\n return false;\n }\n\n // Already dead? Nothing to do.\n if (!isProcessAlive(pid)) {\n return true;\n }\n\n // Step 1: Send SIGTERM (or taskkill /F on Windows)\n killProcess(pid);\n\n // Step 2: Wait for graceful shutdown\n const died = await waitForProcessToDie(pid, grace);\n if (died) {\n return true;\n }\n\n // Step 3: Escalate to SIGKILL (Unix) / re-attempt taskkill (Windows)\n forceKillProcess(pid);\n return await waitForProcessToDie(pid, FORCE_KILL_WAIT_MS);\n}\n\nexport interface SpawnResult {\n child: ChildProcess;\n pid: number;\n}\n\n/**\n * Spawn a command with stdio forwarding\n */\nexport function spawnCommand(command: string, args: string[]): SpawnResult {\n const child = spawn(command, args, {\n stdio: 'inherit',\n shell: isWindows,\n detached: !isWindows,\n });\n\n if (child.pid === undefined) {\n throw new Error('Failed to spawn process');\n }\n\n return {\n child,\n pid: child.pid,\n };\n}\n\n/**\n * Spawn a command in daemon mode (detached, with output captured to log file).\n * The parent process does not wait for the child — it calls child.unref().\n */\nexport function spawnCommandDaemon(\n command: string,\n args: string[],\n logFilePath: string\n): SpawnResult {\n const logFd = openSync(logFilePath, 'a');\n\n try {\n const stdio: StdioOptions = ['ignore', logFd, logFd];\n\n const child = spawn(command, args, {\n stdio,\n // Don't use shell on Windows for daemon mode. With shell: true, Node spawns\n // cmd.exe which doesn't reliably pass fd-based stdio to grandchild processes\n // when combined with detached: true (known Node.js issue on Windows).\n // CreateProcess still searches PATH, so executables are found without a shell.\n detached: true,\n });\n\n if (child.pid === undefined) {\n throw new Error('Failed to spawn daemon process');\n }\n\n child.unref();\n\n return {\n child,\n pid: child.pid,\n };\n } finally {\n closeSync(logFd);\n }\n}\n\n// Grace period for Windows child process to exit before force-killing\nconst WINDOWS_GRACEFUL_TIMEOUT_MS = 2000;\n\n/**\n * Set up signal handlers to forward signals to child process\n *\n * Unix: forwards SIGTERM to child for graceful shutdown.\n *\n * Windows: the child shares the console (stdio: 'inherit'), so when the user\n * presses Ctrl+C, Windows delivers CTRL_C_EVENT to the child directly — no\n * forwarding needed. We just set a force-kill timeout as a safety net in case\n * the child doesn't exit on its own. process.kill(pid, 'SIGINT') on Windows\n * calls TerminateProcess (not GenerateConsoleCtrlEvent), so we intentionally\n * avoid calling it to give the child time to handle the OS-delivered signal.\n */\nexport function setupSignalHandlers(child: ChildProcess, onExit?: () => void): void {\n let forceKillTimer: ReturnType<typeof setTimeout> | null = null;\n\n const forceKillWindows = () => {\n if (child.pid && isValidPid(child.pid) && isProcessAlive(child.pid)) {\n try {\n execSync(`taskkill /PID ${child.pid} /T /F`, {\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } catch {\n // Process might already be dead\n }\n }\n };\n\n const handleSignal = (_signal: NodeJS.Signals) => {\n if (child.pid && isValidPid(child.pid)) {\n if (isWindows) {\n // On Windows, the child already received CTRL_C_EVENT from the OS\n // (since it shares our console via stdio: 'inherit').\n // Don't call process.kill() — it uses TerminateProcess which would\n // prevent the child from running its cleanup handlers.\n // Just set a force-kill timeout as a safety net.\n if (forceKillTimer === null) {\n forceKillTimer = setTimeout(forceKillWindows, WINDOWS_GRACEFUL_TIMEOUT_MS);\n forceKillTimer.unref();\n }\n } else {\n // Forward as SIGTERM for graceful shutdown\n child.kill('SIGTERM');\n }\n }\n };\n\n // Forward both SIGINT (Ctrl+C) and SIGTERM to child\n process.on('SIGINT', () => handleSignal('SIGINT'));\n process.on('SIGTERM', () => handleSignal('SIGTERM'));\n\n child.on('exit', (code, signal) => {\n // Child exited gracefully — cancel the force-kill timer if pending\n if (forceKillTimer !== null) {\n clearTimeout(forceKillTimer);\n forceKillTimer = null;\n }\n if (onExit) {\n onExit();\n }\n if (signal) {\n process.exit(128 + (signal === 'SIGTERM' ? 15 : signal === 'SIGINT' ? 2 : 1));\n }\n process.exit(code ?? 0);\n });\n\n child.on('error', err => {\n console.error(`Failed to start process: ${err.message}`);\n process.exit(1);\n });\n}\n","/**\n * Log file operations for just-one daemon mode\n */\n\nimport {\n existsSync,\n statSync,\n renameSync,\n unlinkSync,\n readFileSync,\n openSync,\n readSync,\n closeSync,\n} from 'fs';\nimport { join } from 'path';\n\nconst DEFAULT_MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB\n\n/**\n * Get the path to a log file for a given name\n */\nexport function getLogFilePath(name: string, pidDir: string): string {\n return join(pidDir, `${name}.log`);\n}\n\n/**\n * Get the path to a backup log file for a given name\n */\nexport function getBackupLogFilePath(name: string, pidDir: string): string {\n return join(pidDir, `${name}.log.1`);\n}\n\n/**\n * Get the size of a log file in bytes\n * Returns 0 if the file doesn't exist\n */\nexport function getLogFileSize(name: string, pidDir: string): number {\n const logPath = getLogFilePath(name, pidDir);\n try {\n const stats = statSync(logPath);\n return stats.size;\n } catch {\n return 0;\n }\n}\n\n/**\n * Rotate the log file if it exceeds the maximum size.\n * Renames current .log to .log.1 (overwriting any existing backup).\n * Returns true if rotation occurred.\n */\nexport function rotateLogIfNeeded(\n name: string,\n pidDir: string,\n maxSize: number = DEFAULT_MAX_LOG_SIZE\n): boolean {\n const size = getLogFileSize(name, pidDir);\n if (size <= maxSize) {\n return false;\n }\n\n const logPath = getLogFilePath(name, pidDir);\n const backupPath = getBackupLogFilePath(name, pidDir);\n\n // Remove existing backup if present\n try {\n if (existsSync(backupPath)) {\n unlinkSync(backupPath);\n }\n } catch {\n // Ignore errors removing old backup\n }\n\n renameSync(logPath, backupPath);\n return true;\n}\n\n/**\n * Read lines from a log file.\n * If lastN is provided, returns only the last N lines.\n * Returns empty array if the file doesn't exist.\n */\nexport function readLogLines(name: string, pidDir: string, lastN?: number): string[] {\n const logPath = getLogFilePath(name, pidDir);\n\n if (!existsSync(logPath)) {\n return [];\n }\n\n try {\n const content = readFileSync(logPath, 'utf8');\n if (content.length === 0) {\n return [];\n }\n\n const lines = content.split('\\n');\n // Remove trailing empty line from final newline\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n if (lastN === undefined) {\n return lines;\n }\n if (lastN === 0) {\n return [];\n }\n return lines.slice(-lastN);\n } catch {\n return [];\n }\n}\n\nexport interface TailOptions {\n onLine: (line: string) => void;\n onError?: (err: Error) => void;\n initialLines?: number;\n pollIntervalMs?: number;\n}\n\nexport interface TailHandle {\n stop: () => void;\n}\n\n/**\n * Follow a log file in real-time using setInterval polling.\n * Reads from last byte offset on each change, calling onLine for each new line.\n * Optionally emits the last N lines of existing content first.\n */\nexport function tailLogFile(name: string, pidDir: string, options: TailOptions): TailHandle {\n const logPath = getLogFilePath(name, pidDir);\n const pollIntervalMs = options.pollIntervalMs ?? 500;\n\n // Emit initial lines if requested\n if (options.initialLines !== undefined && options.initialLines > 0) {\n const initial = readLogLines(name, pidDir, options.initialLines);\n for (const line of initial) {\n options.onLine(line);\n }\n }\n\n // Track current file offset\n let offset = 0;\n try {\n if (existsSync(logPath)) {\n offset = statSync(logPath).size;\n }\n } catch {\n // File may not exist yet, start from 0\n }\n\n let partialLine = '';\n\n const checkForChanges = () => {\n let newSize: number;\n try {\n if (!existsSync(logPath)) return;\n newSize = statSync(logPath).size;\n } catch {\n return;\n }\n\n if (newSize <= offset) {\n // File was truncated or unchanged — reset to new size\n offset = newSize;\n return;\n }\n\n const bytesToRead = newSize - offset;\n try {\n const fd = openSync(logPath, 'r');\n try {\n const buf = Buffer.alloc(bytesToRead);\n readSync(fd, buf, 0, bytesToRead, offset);\n offset = newSize;\n\n const chunk = buf.toString('utf8');\n const parts = chunk.split('\\n');\n\n // Prepend any partial line from last read\n if (parts.length > 0) {\n parts[0] = partialLine + parts[0]!;\n partialLine = '';\n }\n\n // Last element is either empty (if chunk ended with \\n) or a partial line\n const lastPart = parts.pop();\n if (lastPart !== undefined && lastPart !== '') {\n partialLine = lastPart;\n }\n\n for (const line of parts) {\n options.onLine(line);\n }\n } finally {\n closeSync(fd);\n }\n } catch (err) {\n if (options.onError && err instanceof Error) {\n options.onError(err);\n }\n }\n };\n\n const intervalId = setInterval(checkForChanges, pollIntervalMs);\n\n return {\n stop: () => {\n clearInterval(intervalId);\n },\n };\n}\n\n/**\n * Delete log files (.log and .log.1) for a given name.\n * Silently ignores missing files.\n */\nexport function deleteLogFiles(name: string, pidDir: string): void {\n const logPath = getLogFilePath(name, pidDir);\n const backupPath = getBackupLogFilePath(name, pidDir);\n\n try {\n if (existsSync(logPath)) {\n unlinkSync(logPath);\n }\n } catch {\n // Ignore\n }\n\n try {\n if (existsSync(backupPath)) {\n unlinkSync(backupPath);\n }\n } catch {\n // Ignore\n }\n}\n"],"mappings":"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","names":["existsSync","existsSync","statSync","unlinkSync","readFileSync","openSync","closeSync","join","require","pid","existsSync"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@radleta/just-one",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "A CLI tool that ensures only one instance of a command runs at a time",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"./package.json": "./package.json"
|
|
17
17
|
},
|
|
18
18
|
"engines": {
|
|
19
|
-
"node": ">=
|
|
19
|
+
"node": ">=20.0.0"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
22
|
"dev": "tsup --watch",
|