clipboardy
Version:
Access the system clipboard (copy/paste)
133 lines (113 loc) • 3.77 kB
JavaScript
import fs from 'node:fs';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import {execa, execaSync} from 'execa';
import {is64bitSync} from 'is64bit';
import {powerShellPath, executePowerShell} from 'powershell-utils';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const binarySuffix = process.arch === 'arm64' ? 'aarch64' : (is64bitSync() ? 'x86_64' : 'i686');
// Binaries from: https://github.com/sindresorhus/win-clipboard
const windowBinaryPath = path.join(__dirname, `../fallbacks/windows/clipboard_${binarySuffix}.exe`);
// Check if the bundled fallback binary exists on disk. It may not exist when running in a bundled environment (e.g. Node.js SEA, webpack, esbuild).
const hasWindowsBinaryFallback = fs.existsSync(windowBinaryPath);
// Robust PowerShell commands with error handling
// Read raw stdin from the standard input stream to preserve newline characters and trailing newlines.
// Avoid $Input because it enumerates pipeline input line-by-line.
const psCopyScript = `
try {
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
$inputStream = [Console]::OpenStandardInput()
$memoryStream = New-Object System.IO.MemoryStream
$inputStream.CopyTo($memoryStream)
$text = [Console]::InputEncoding.GetString($memoryStream.ToArray())
Set-Clipboard -Value $text
} catch {
Write-Error "Failed to set clipboard: $($_.Exception.Message)"
exit 1
}
`.trim();
const psPasteScript = `
try {
$content = Get-Clipboard -Raw -ErrorAction Stop
if ($content -eq $null) { $content = '' }
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::Out.Write($content)
} catch {
Write-Error "Failed to get clipboard: $($_.Exception.Message)"
exit 1
}
`.trim();
const executeWithFallback = async (primaryCommand, fallbackCommand) => {
try {
return await primaryCommand();
} catch {
if (!hasWindowsBinaryFallback) {
throw new Error('PowerShell clipboard operation failed and the bundled fallback binary was not found. This can happen when running in a bundled environment (e.g. Node.js SEA).');
}
return fallbackCommand();
}
};
const executeWithFallbackSync = (primaryCommand, fallbackCommand) => {
try {
return primaryCommand();
} catch {
if (!hasWindowsBinaryFallback) {
throw new Error('PowerShell clipboard operation failed and the bundled fallback binary was not found. This can happen when running in a bundled environment (e.g. Node.js SEA).');
}
return fallbackCommand();
}
};
const runPowerShell = (script, options) => execa(
powerShellPath(),
executePowerShell.createArguments(script),
{
windowsHide: true,
...options,
},
);
const runPowerShellSync = (script, options) => execaSync(
powerShellPath(),
executePowerShell.createArguments(script),
{
windowsHide: true,
...options,
},
);
const runBinary = (arguments_, options) => execa(
windowBinaryPath,
arguments_,
{
windowsHide: true,
...options,
},
);
const runBinarySync = (arguments_, options) => execaSync(
windowBinaryPath,
arguments_,
{
windowsHide: true,
...options,
},
);
const clipboard = {
copy: async options => executeWithFallback(
async () => runPowerShell(psCopyScript, options),
async () => runBinary(['--copy'], options),
),
async paste(options) {
const {stdout} = await executeWithFallback(
async () => runPowerShell(psPasteScript, options),
async () => runBinary(['--paste'], options),
);
return stdout;
},
copySync: options => executeWithFallbackSync(
() => runPowerShellSync(psCopyScript, options),
() => runBinarySync(['--copy'], options),
),
pasteSync: options => executeWithFallbackSync(
() => runPowerShellSync(psPasteScript, options),
() => runBinarySync(['--paste'], options),
).stdout,
};
export default clipboard;