+36
-9
@@ -10,2 +10,16 @@ import { spawn } from 'node:child_process'; | ||
| } | ||
| /** | ||
| * Builds an Error describing a clipboard tool that exited with a non-zero code, | ||
| * surfacing the command, exit code and any stderr output as the failure cause. | ||
| */ | ||
| function createUnknownError(command, code, stderr) { | ||
| const trimmed = stderr.trim(); | ||
| const details = [ | ||
| `command \`${command[0]}\` exited with code ${code ?? 'unknown'}`, | ||
| trimmed && `stderr: ${trimmed}` | ||
| ] | ||
| .filter(Boolean) | ||
| .join('. '); | ||
| return new Error(details); | ||
| } | ||
| const WINDOWS_READ_COMMAND = [ | ||
@@ -56,8 +70,10 @@ 'powershell', | ||
| }); | ||
| let data = ''; | ||
| proc.stdout.on('data', (chunk) => (data += chunk)); | ||
| let stdout = ''; | ||
| let stderr = ''; | ||
| proc.stdout.on('data', (chunk) => (stdout += chunk)); | ||
| proc.stderr.on('data', (chunk) => (stderr += chunk)); | ||
| proc.on('error', (cause) => reject(new Error('An error occurred while reading from clipboard', { cause }))); | ||
| proc.on('close', (code) => code === 0 | ||
| ? resolve(data.trim()) | ||
| : reject(new Error('An unknown error occurred while reading from clipboard'))); | ||
| ? resolve(stdout.trim()) | ||
| : reject(createUnknownError(command, code, stderr))); | ||
| }); | ||
@@ -70,3 +86,5 @@ } | ||
| '-Command', | ||
| '[Console]::InputEncoding = [Text.UTF8Encoding]::new($false); [Console]::In.ReadToEnd() | Set-Clipboard' | ||
| // `Set-Clipboard` rejects empty input, so clear the clipboard instead | ||
| // when there is nothing to write (e.g. `writeText('')`). | ||
| '[Console]::InputEncoding = [Text.UTF8Encoding]::new($false); $text = [Console]::In.ReadToEnd(); if ($text.Length -gt 0) { $text | Set-Clipboard } else { Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Clipboard]::Clear() }' | ||
| ] | ||
@@ -109,9 +127,18 @@ ]; | ||
| const proc = spawn(...command, { | ||
| stdio: ['pipe', 'ignore', 'ignore'], | ||
| stdio: ['pipe', 'ignore', 'pipe'], | ||
| signal: AbortSignal.timeout(TIMEOUT) | ||
| }); | ||
| let stderr = ''; | ||
| proc.stderr.on('data', (chunk) => (stderr += chunk)); | ||
| proc.on('error', (cause) => reject(new Error('An error occurred while copying', { cause }))); | ||
| proc.on('close', (code) => code === 0 | ||
| ? resolve() | ||
| : reject(new Error('An unknown error occurred while copying'))); | ||
| // Use 'exit' rather than 'close': some tools (e.g. xclip) daemonize a child | ||
| // that keeps the stderr pipe open, so 'close' would never fire. | ||
| proc.on('exit', (code) => { | ||
| // The daemonized child inherits the stderr pipe, keeping it (and the event | ||
| // loop) open after the parent exits. Destroy it so callers can exit cleanly. | ||
| proc.stderr?.destroy(); | ||
| code === 0 | ||
| ? resolve() | ||
| : reject(createUnknownError(command, code, stderr)); | ||
| }); | ||
| proc.stdin.write(text); | ||
@@ -118,0 +145,0 @@ proc.stdin.end(); |
+1
-1
| { | ||
| "name": "tinyclip", | ||
| "packageManager": "pnpm@10.30.1", | ||
| "version": "0.1.14", | ||
| "version": "0.1.15", | ||
| "description": "A tiny utility to interact with the system clipboard.", | ||
@@ -6,0 +6,0 @@ "keywords": [ |
8131
19.17%151
21.77%