clipboardy 4.0.0 → 5.0.1

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/index.d.ts CHANGED
@@ -9,9 +9,6 @@ declare const clipboard: {
9
9
  import clipboard from 'clipboardy';
10
10
 
11
11
  await clipboard.write('🦄');
12
-
13
- await clipboard.read();
14
- //=> '🦄'
15
12
  ```
16
13
  */
17
14
  write(text: string): Promise<void>;
@@ -23,9 +20,7 @@ declare const clipboard: {
23
20
  ```
24
21
  import clipboard from 'clipboardy';
25
22
 
26
- await clipboard.write('🦄');
27
-
28
- await clipboard.read();
23
+ const content = await clipboard.read();
29
24
  //=> '🦄'
30
25
  ```
31
26
  */
@@ -43,9 +38,6 @@ declare const clipboard: {
43
38
  import clipboard from 'clipboardy';
44
39
 
45
40
  clipboard.writeSync('🦄');
46
-
47
- clipboard.readSync();
48
- //=> '🦄'
49
41
  ```
50
42
  */
51
43
  writeSync(text: string): void;
@@ -59,9 +51,7 @@ declare const clipboard: {
59
51
  ```
60
52
  import clipboard from 'clipboardy';
61
53
 
62
- clipboard.writeSync('🦄');
63
-
64
- clipboard.readSync();
54
+ const content = clipboard.readSync();
65
55
  //=> '🦄'
66
56
  ```
67
57
  */
package/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import process from 'node:process';
2
2
  import isWSL from 'is-wsl';
3
+ import isWayland from 'is-wayland';
3
4
  import termux from './lib/termux.js';
4
5
  import linux from './lib/linux.js';
6
+ import wayland from './lib/wayland.js';
5
7
  import macos from './lib/macos.js';
6
8
  import windows from './lib/windows.js';
7
9
 
@@ -16,7 +18,7 @@ const platformLib = (() => {
16
18
  }
17
19
 
18
20
  case 'android': {
19
- if (process.env.PREFIX !== '/data/data/com.termux/files/usr') {
21
+ if (process.env.TERMUX__PREFIX === undefined) {
20
22
  throw new Error('You need to install Termux for this module to work on Android: https://termux.com');
21
23
  }
22
24
 
@@ -29,31 +31,40 @@ const platformLib = (() => {
29
31
  return windows;
30
32
  }
31
33
 
34
+ // Check for Wayland session on Linux
35
+ if (isWayland()) {
36
+ return wayland;
37
+ }
38
+
32
39
  return linux;
33
40
  }
34
41
  }
35
42
  })();
36
43
 
37
- const clipboard = {};
44
+ const clipboard = {
45
+ async write(text) {
46
+ if (typeof text !== 'string') {
47
+ throw new TypeError(`Expected a string, got ${typeof text}`);
48
+ }
38
49
 
39
- clipboard.write = async text => {
40
- if (typeof text !== 'string') {
41
- throw new TypeError(`Expected a string, got ${typeof text}`);
42
- }
50
+ await platformLib.copy({input: text});
51
+ },
43
52
 
44
- await platformLib.copy({input: text});
45
- };
53
+ async read() {
54
+ return platformLib.paste({stripFinalNewline: false});
55
+ },
46
56
 
47
- clipboard.read = async () => platformLib.paste({stripFinalNewline: false});
57
+ writeSync(text) {
58
+ if (typeof text !== 'string') {
59
+ throw new TypeError(`Expected a string, got ${typeof text}`);
60
+ }
48
61
 
49
- clipboard.writeSync = text => {
50
- if (typeof text !== 'string') {
51
- throw new TypeError(`Expected a string, got ${typeof text}`);
52
- }
62
+ platformLib.copySync({input: text});
63
+ },
53
64
 
54
- platformLib.copySync({input: text});
65
+ readSync() {
66
+ return platformLib.pasteSync({stripFinalNewline: false});
67
+ },
55
68
  };
56
69
 
57
- clipboard.readSync = () => platformLib.pasteSync({stripFinalNewline: false});
58
-
59
70
  export default clipboard;
package/lib/linux.js CHANGED
@@ -11,11 +11,13 @@ const copyArguments = ['--clipboard', '--input'];
11
11
  const pasteArguments = ['--clipboard', '--output'];
12
12
 
13
13
  const makeError = (xselError, fallbackError) => {
14
- let error;
15
- if (xselError.code === 'ENOENT') {
16
- error = new Error('Couldn\'t find the `xsel` binary and fallback didn\'t work. On Debian/Ubuntu you can install xsel with: sudo apt install xsel');
17
- } else {
18
- error = new Error('Both xsel and fallback failed');
14
+ const message = xselError.code === 'ENOENT'
15
+ ? 'Couldn\'t find the `xsel` binary and fallback didn\'t work. On Debian/Ubuntu you can install xsel with: sudo apt install xsel'
16
+ : 'Both xsel and fallback failed';
17
+
18
+ const error = new Error(message);
19
+
20
+ if (xselError.code !== 'ENOENT') {
19
21
  error.xselError = xselError;
20
22
  }
21
23
 
package/lib/termux.js CHANGED
@@ -2,7 +2,7 @@ import {execa, execaSync} from 'execa';
2
2
 
3
3
  const handler = error => {
4
4
  if (error.code === 'ENOENT') {
5
- throw new Error('Couldn\'t find the termux-api scripts. You can install them with: apt install termux-api');
5
+ throw new Error('Couldn\'t find the `termux-api` scripts. You can install them with: apt install termux-api');
6
6
  }
7
7
 
8
8
  throw error;
package/lib/wayland.js ADDED
@@ -0,0 +1,70 @@
1
+ import {execa, execaSync} from 'execa';
2
+ import linux from './linux.js';
3
+
4
+ // Common arguments for text clipboard operations
5
+ const textArgs = ['--type', 'text/plain'];
6
+
7
+ const makeError = (command, error) => {
8
+ if (error.code === 'ENOENT') {
9
+ return new Error(`Couldn't find the \`${command}\` binary. On Debian/Ubuntu you can install wl-clipboard with: sudo apt install wl-clipboard`);
10
+ }
11
+
12
+ return new Error(`Command \`${command}\` failed: ${error.message}`);
13
+ };
14
+
15
+ const tryWaylandWithFallback = async (command, arguments_, options, fallbackMethod) => {
16
+ try {
17
+ const result = await execa(command, arguments_, options);
18
+ return result.stdout;
19
+ } catch (error) {
20
+ // Handle empty clipboard on wl-paste
21
+ if (command === 'wl-paste' && /nothing is copied|no selection|selection owner/i.test(error.stderr || '')) {
22
+ return '';
23
+ }
24
+
25
+ // Fall back to X11 if wl-clipboard not found OR Wayland not available
26
+ if (error.code === 'ENOENT'
27
+ || /wayland|wayland_display|failed to connect|display/i.test(error.stderr || '')) {
28
+ return fallbackMethod(options);
29
+ }
30
+
31
+ throw makeError(command, error);
32
+ }
33
+ };
34
+
35
+ const tryWaylandWithFallbackSync = (command, arguments_, options, fallbackMethod) => {
36
+ try {
37
+ const result = execaSync(command, arguments_, options);
38
+ return result.stdout;
39
+ } catch (error) {
40
+ // Handle empty clipboard on wl-paste
41
+ if (command === 'wl-paste' && /nothing is copied|no selection|selection owner/i.test(error.stderr || '')) {
42
+ return '';
43
+ }
44
+
45
+ // Fall back to X11 if wl-clipboard not found OR Wayland not available
46
+ if (error.code === 'ENOENT'
47
+ || /wayland|wayland_display|failed to connect|display/i.test(error.stderr || '')) {
48
+ return fallbackMethod(options);
49
+ }
50
+
51
+ throw makeError(command, error);
52
+ }
53
+ };
54
+
55
+ const clipboard = {
56
+ async copy(options) {
57
+ await tryWaylandWithFallback('wl-copy', textArgs, options, linux.copy);
58
+ },
59
+ copySync(options) {
60
+ tryWaylandWithFallbackSync('wl-copy', textArgs, options, linux.copySync);
61
+ },
62
+ async paste(options) {
63
+ return tryWaylandWithFallback('wl-paste', [...textArgs, '--no-newline'], options, linux.paste);
64
+ },
65
+ pasteSync(options) {
66
+ return tryWaylandWithFallbackSync('wl-paste', [...textArgs, '--no-newline'], options, linux.pasteSync);
67
+ },
68
+ };
69
+
70
+ export default clipboard;
package/lib/windows.js CHANGED
@@ -1,3 +1,4 @@
1
+ import {Buffer} from 'node:buffer';
1
2
  import path from 'node:path';
2
3
  import {fileURLToPath} from 'node:url';
3
4
  import {execa, execaSync} from 'execa';
@@ -10,14 +11,85 @@ const binarySuffix = is64bitSync() ? 'x86_64' : 'i686';
10
11
  // Binaries from: https://github.com/sindresorhus/win-clipboard
11
12
  const windowBinaryPath = path.join(__dirname, `../fallbacks/windows/clipboard_${binarySuffix}.exe`);
12
13
 
14
+ const powershellPath = 'powershell.exe';
15
+
16
+ const psCommonArgs = [
17
+ '-NoProfile',
18
+ '-NonInteractive',
19
+ '-ExecutionPolicy',
20
+ 'Bypass',
21
+ ];
22
+
23
+ // Use -EncodedCommand for better safety and Unicode handling
24
+ const createEncodedCommand = script => {
25
+ const encodedCommand = Buffer.from(script, 'utf16le').toString('base64');
26
+ return [...psCommonArgs, '-EncodedCommand', encodedCommand];
27
+ };
28
+
29
+ // Robust PowerShell commands with error handling
30
+ const psCopyScript = `
31
+ try {
32
+ [Console]::InputEncoding = [System.Text.Encoding]::UTF8
33
+ $input = [Console]::In.ReadToEnd()
34
+ if ($input -eq $null) { $input = '' }
35
+ Set-Clipboard -Value $input
36
+ } catch {
37
+ Write-Error "Failed to set clipboard: $($_.Exception.Message)"
38
+ exit 1
39
+ }
40
+ `.trim();
41
+
42
+ const psPasteScript = `
43
+ try {
44
+ $content = Get-Clipboard -Raw -ErrorAction Stop
45
+ if ($content -eq $null) { $content = '' }
46
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
47
+ [Console]::Out.Write($content)
48
+ } catch {
49
+ Write-Error "Failed to get clipboard: $($_.Exception.Message)"
50
+ exit 1
51
+ }
52
+ `.trim();
53
+
54
+ const executeWithFallback = async (primaryCommand, fallbackCommand) => {
55
+ try {
56
+ return await primaryCommand();
57
+ } catch {
58
+ return fallbackCommand();
59
+ }
60
+ };
61
+
62
+ const executeWithFallbackSync = (primaryCommand, fallbackCommand) => {
63
+ try {
64
+ return primaryCommand();
65
+ } catch {
66
+ return fallbackCommand();
67
+ }
68
+ };
69
+
13
70
  const clipboard = {
14
- copy: async options => execa(windowBinaryPath, ['--copy'], options),
71
+ copy: async options => executeWithFallback(
72
+ async () => execa(powershellPath, createEncodedCommand(psCopyScript), options),
73
+ async () => execa(windowBinaryPath, ['--copy'], options),
74
+ ),
75
+
15
76
  async paste(options) {
16
- const {stdout} = await execa(windowBinaryPath, ['--paste'], options);
77
+ const {stdout} = await executeWithFallback(
78
+ async () => execa(powershellPath, createEncodedCommand(psPasteScript), options),
79
+ async () => execa(windowBinaryPath, ['--paste'], options),
80
+ );
17
81
  return stdout;
18
82
  },
19
- copySync: options => execaSync(windowBinaryPath, ['--copy'], options),
20
- pasteSync: options => execaSync(windowBinaryPath, ['--paste'], options).stdout,
83
+
84
+ copySync: options => executeWithFallbackSync(
85
+ () => execaSync(powershellPath, createEncodedCommand(psCopyScript), options),
86
+ () => execaSync(windowBinaryPath, ['--copy'], options),
87
+ ),
88
+
89
+ pasteSync: options => executeWithFallbackSync(
90
+ () => execaSync(powershellPath, createEncodedCommand(psPasteScript), options),
91
+ () => execaSync(windowBinaryPath, ['--paste'], options),
92
+ ).stdout,
21
93
  };
22
94
 
23
95
  export default clipboard;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clipboardy",
3
- "version": "4.0.0",
3
+ "version": "5.0.1",
4
4
  "description": "Access the system clipboard (copy/paste)",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/clipboardy",
@@ -16,12 +16,13 @@
16
16
  "node": "./index.js",
17
17
  "default": "./browser.js"
18
18
  },
19
+ "sideEffects": false,
19
20
  "engines": {
20
- "node": ">=18"
21
+ "node": ">=20"
21
22
  },
22
- "sideEffects": false,
23
23
  "scripts": {
24
- "test": "xo && ava && tsd"
24
+ "//test": "xo && ava",
25
+ "test": "ava"
25
26
  },
26
27
  "files": [
27
28
  "index.js",
@@ -44,14 +45,14 @@
44
45
  "xsel"
45
46
  ],
46
47
  "dependencies": {
47
- "execa": "^8.0.1",
48
+ "execa": "^9.6.0",
49
+ "is-wayland": "^0.1.0",
48
50
  "is-wsl": "^3.1.0",
49
51
  "is64bit": "^2.0.0"
50
52
  },
51
53
  "devDependencies": {
52
- "ava": "^5.3.1",
53
- "tsd": "^0.29.0",
54
- "xo": "^0.56.0"
54
+ "ava": "^6.4.1",
55
+ "xo": "^1.2.2"
55
56
  },
56
57
  "ava": {
57
58
  "serial": true
package/readme.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Access the system clipboard (copy/paste)
4
4
 
5
- Cross-platform. Supports: macOS, Windows, Linux, OpenBSD, FreeBSD, Android with [Termux](https://termux.com/), and [modern browsers](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API#Browser_compatibility).
5
+ Cross-platform. Supports: macOS, Windows, Linux (including Wayland), OpenBSD, FreeBSD, Android with [Termux](https://termux.com/), and [modern browsers](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API#Browser_compatibility).
6
6
 
7
7
  ## Install
8
8
 
@@ -15,6 +15,12 @@ npm install clipboardy
15
15
  ```js
16
16
  import clipboard from 'clipboardy';
17
17
 
18
+ await clipboard.write('🦄');
19
+
20
+ await clipboard.read();
21
+ //=> '🦄'
22
+
23
+ // Or use the synchronous API
18
24
  clipboard.writeSync('🦄');
19
25
 
20
26
  clipboard.readSync();
@@ -23,7 +29,7 @@ clipboard.readSync();
23
29
 
24
30
  ## API
25
31
 
26
- In the browser, it requires a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).
32
+ **Browser usage:** Requires a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts) (HTTPS). Synchronous methods are not available in browsers.
27
33
 
28
34
  ### clipboard
29
35
 
@@ -31,7 +37,7 @@ In the browser, it requires a [secure context](https://developer.mozilla.org/en-
31
37
 
32
38
  Write (copy) to the clipboard asynchronously.
33
39
 
34
- Returns a `Promise`.
40
+ Returns a `Promise<void>`.
35
41
 
36
42
  ##### text
37
43
 
@@ -39,11 +45,20 @@ Type: `string`
39
45
 
40
46
  The text to write to the clipboard.
41
47
 
48
+ ```js
49
+ await clipboard.write('🦄');
50
+ ```
51
+
42
52
  #### .read()
43
53
 
44
54
  Read (paste) from the clipboard asynchronously.
45
55
 
46
- Returns a `Promise`.
56
+ Returns a `Promise<string>`.
57
+
58
+ ```js
59
+ const content = await clipboard.read();
60
+ //=> '🦄'
61
+ ```
47
62
 
48
63
  #### .writeSync(text)
49
64
 
@@ -57,19 +72,37 @@ Type: `string`
57
72
 
58
73
  The text to write to the clipboard.
59
74
 
75
+ ```js
76
+ clipboard.writeSync('🦄');
77
+ ```
78
+
60
79
  #### .readSync()
61
80
 
62
81
  Read (paste) from the clipboard synchronously.
63
82
 
83
+ Returns a `string`.
84
+
64
85
  **Doesn't work in browsers.**
65
86
 
87
+ ```js
88
+ const content = clipboard.readSync();
89
+ //=> '🦄'
90
+ ```
91
+
66
92
  ## FAQ
67
93
 
68
94
  #### Where can I find the source of the bundled binaries?
69
95
 
70
96
  The [Linux binary](fallbacks/linux/xsel) is just a bundled version of [`xsel`](https://linux.die.net/man/1/xsel). The source for the [Windows binary](fallbacks/windows/clipboard_x86_64.exe) can be found [here](https://github.com/sindresorhus/win-clipboard).
71
97
 
98
+ On Windows, clipboardy first tries the native PowerShell cmdlets (`Set-Clipboard`/`Get-Clipboard`) and falls back to the bundled binary if PowerShell is unavailable or restricted.
99
+
100
+ #### Does this work on Wayland?
101
+
102
+ Yes. On Linux, clipboardy automatically detects Wayland sessions and uses [`wl-clipboard`](https://github.com/bugaevc/wl-clipboard) when available. If not, it gracefully falls back to X11 tools. Also works with WSLg (Windows Subsystem for Linux GUI). Install `wl-clipboard` using your distribution's package manager.
103
+
72
104
  ## Related
73
105
 
74
106
  - [clipboard-cli](https://github.com/sindresorhus/clipboard-cli) - CLI for this module
107
+ - [clipboard-image](https://github.com/sindresorhus/clipboard-image) - Get and set images on the clipboard
75
108
  - [copy-text-to-clipboard](https://github.com/sindresorhus/copy-text-to-clipboard) - Copy text to the clipboard in the browser