diff --git a/index.js b/index.js index 1ad2ab9..9760546 100644 --- a/index.js +++ b/index.js @@ -76,6 +76,15 @@ async function platformSpecificImplementation() { return import('./lib/windows.js'); } + case 'linux': { + const {isWsl} = await import('wsl-utils'); + if (isWsl) { + return import('./lib/wsl.js'); + } + + return import('./lib/linux.js'); + } + default: { return import('./lib/linux.js'); } diff --git a/lib/wsl.js b/lib/wsl.js new file mode 100644 index 0000000..27dbc5b --- /dev/null +++ b/lib/wsl.js @@ -0,0 +1,83 @@ +import {Buffer} from 'node:buffer'; +import chunkify from '@sindresorhus/chunkify'; +import {canAccessPowerShell, convertWslPathToWindows, isUncPath} from 'wsl-utils'; +import {executePowerShell} from 'powershell-utils'; + +/** +WSL implementation: +- Converts WSL paths to Windows paths +- For Windows-local paths (e.g., `C:\…`), uses PowerShell to send to Recycle Bin +- For UNC `\\wsl$\…` paths (Linux filesystem), falls back to the Linux trash implementation +- Processes inputs in chunks to avoid command-line length limits +- Uses `-LiteralPath` to avoid wildcard expansion +- Uses `-EncodedCommand` with UTF-16LE to avoid quoting/length issues +*/ +export default async function wsl(paths) { + const interopEnabled = await canAccessPowerShell(); + if (!interopEnabled) { + const error = new Error('WSL interop is disabled. Enable it or use Linux trash implementation.'); + error.code = 'WSL_INTEROP_DISABLED'; + throw error; + } + + let linuxTrash; + + for (const chunk of chunkify(paths, 400)) { + // eslint-disable-next-line no-await-in-loop + const windowsPaths = await convertWslPathToWindows(chunk); + + // Partition into local drive paths and UNC \\wsl$ paths + const localWindowsPaths = []; + const uncLinuxPaths = []; + + for (const [index, windowsPath] of windowsPaths.entries()) { + if (isUncPath(windowsPath)) { + uncLinuxPaths.push(chunk[index]); + } else { + localWindowsPaths.push(windowsPath); + } + } + + // Fallback to Linux trash for files that live on the Linux filesystem (UNC \\wsl$) + if (uncLinuxPaths.length > 0) { + if (!linuxTrash) { + // eslint-disable-next-line no-await-in-loop + const {default: linuxTrashImport} = await import('./linux.js'); + linuxTrash = linuxTrashImport; + } + + // eslint-disable-next-line no-await-in-loop + await linuxTrash(uncLinuxPaths); + } + + // Nothing to recycle on Windows side for this chunk + if (localWindowsPaths.length === 0) { + continue; + } + + // Build a PowerShell script that: + // - Decodes a Base64 JSON array of paths + // - Uses LiteralPath to avoid wildcard expansion + // - Sends files/dirs to Recycle Bin + const json = JSON.stringify(localWindowsPaths); + const base64Json = Buffer.from(json, 'utf8').toString('base64'); + + const psScript = ` +$ErrorActionPreference = 'Stop' +Add-Type -AssemblyName Microsoft.VisualBasic +$paths = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${base64Json}')) | ConvertFrom-Json +foreach ($p in $paths) { + if (Test-Path -LiteralPath $p) { + if (Test-Path -LiteralPath $p -PathType Container) { + [Microsoft.VisualBasic.FileIO.FileSystem]::DeleteDirectory($p, 'OnlyErrorDialogs', 'SendToRecycleBin') + } else { + [Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile($p, 'OnlyErrorDialogs', 'SendToRecycleBin') + } + } +} +`.trim(); + + // eslint-disable-next-line no-await-in-loop + await executePowerShell(psScript); + } +} diff --git a/package.json b/package.json index 96ade74..c63a071 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,8 @@ "is-path-inside": "^4.0.0", "move-file": "^3.1.0", "p-map": "^7.0.3", + "powershell-utils": "^0.2.0", + "wsl-utils": "^0.4.0", "xdg-trashdir": "^3.1.0" }, "devDependencies": { diff --git a/readme.md b/readme.md index 9898441..ff9aab9 100644 --- a/readme.md +++ b/readme.md @@ -58,6 +58,7 @@ npm install --global trash-cli On macOS, [`macos-trash`](https://github.com/sindresorhus/macos-trash) is used.\ On Linux, the [XDG spec](https://specifications.freedesktop.org/trash/1.0/) is followed.\ On Windows, [`recycle-bin`](https://github.com/sindresorhus/recycle-bin) is used. +On WSL (Windows Subsystem for Linux), files are moved to the Windows Recycle Bin. ## FAQ