open-windows-file-dialog
Version:
Programmatically open a file dialog window (explorer) for picking files. Only works on Windows. Also see: open-finder-dialog, open-linux-file-dialog, and open-file-manager-dialog for other platforms.
171 lines (166 loc) • 5.87 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// index.ts
import cp from "node:child_process";
import path from "node:path";
import util from "node:util";
var execAsync = util.promisify(cp.exec);
var FileDialogError = class extends Error {
static {
__name(this, "FileDialogError");
}
constructor(message) {
super(message);
this.name = "FileDialogError";
}
};
var isCanceled = /* @__PURE__ */ __name((v) => /cancell?ed/i.test(v), "isCanceled");
var escapeForPowerShell = /* @__PURE__ */ __name((str) => {
return str.replace(/\\/g, "\\\\").replace(/"/g, '`"').replace(/\$/g, "`$");
}, "escapeForPowerShell");
var buildFilterString = /* @__PURE__ */ __name((filter) => {
if (filter === "*" || filter === "*.*") {
return "All files (*.*)|*.*";
}
if (filter.includes("|")) {
return filter;
}
const ext = filter.replace("*", "").replace(".", "");
if (ext) {
return `${ext.toUpperCase()} files (${filter})|${filter}`;
}
return "All files (*.*)|*.*";
}, "buildFilterString");
var checkPowerShellAvailable = /* @__PURE__ */ __name(async () => {
try {
const { stdout } = await execAsync('powershell.exe -Command "echo test"', {
timeout: 5e3
});
return stdout.trim() === "test";
} catch {
return false;
}
}, "checkPowerShellAvailable");
var openWindowsFileDialog = /* @__PURE__ */ __name(async (filepath = process.cwd(), options = {}) => {
if (typeof filepath !== "string") {
throw new FileDialogError("Filepath must be a string");
}
const normalizedPath = path.resolve(filepath);
const isPowerShellAvailable = await checkPowerShellAvailable();
if (!isPowerShellAvailable) {
throw new FileDialogError("PowerShell is not available on this system");
}
const opts = {
multiple: true,
checkFileExists: true,
filter: "*.*",
title: "Select File(s)",
defaultExtension: "",
maxTimeout: 5 * 60 * 1e3,
// 5 minutes
...options
};
const escapedPath = escapeForPowerShell(normalizedPath);
const escapedTitle = escapeForPowerShell(opts.title);
const filterString = buildFilterString(opts.filter);
const pathSeparator = "|<<PATH_SEPARATOR>>|";
const powershellScript = `
try {
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.Multiselect = ${opts.multiple ? "$true" : "$false"}
$OpenFileDialog.InitialDirectory = "${escapedPath}"
$OpenFileDialog.Filter = "${escapeForPowerShell(filterString)}"
$OpenFileDialog.CheckFileExists = ${opts.checkFileExists ? "$true" : "$false"}
$OpenFileDialog.CheckPathExists = $true
$OpenFileDialog.SupportMultiDottedExtensions = $true
$OpenFileDialog.Title = "${escapedTitle}"
$OpenFileDialog.RestoreDirectory = $true
${opts.defaultExtension ? `$OpenFileDialog.DefaultExt = "${escapeForPowerShell(opts.defaultExtension)}"` : ""}
# Ensure dialog appears on top
$OpenFileDialog.ShowHelp = $false
$DialogResult = $OpenFileDialog.ShowDialog()
if ($DialogResult -eq [System.Windows.Forms.DialogResult]::OK) {
foreach ($FileName in $OpenFileDialog.FileNames) {
Write-Host "$FileName${pathSeparator}"
}
} else {
Write-Host "CANCELLED"
}
} catch {
Write-Error $_.Exception.Message
exit 1
}
`;
return new Promise((resolve, reject) => {
const child = cp.spawn(
"powershell.exe",
["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", "-"],
{
windowsHide: true,
shell: false
}
);
let stdoutData = "";
let stderrData = "";
let hasResolved = false;
child.stdout.on("data", (data) => {
stdoutData += data.toString();
});
child.stderr.on("data", (data) => {
stderrData += data.toString();
});
child.on("error", (error) => {
if (!hasResolved) {
hasResolved = true;
reject(new FileDialogError(`Failed to spawn PowerShell: ${error.message}`));
}
});
child.on("close", (code) => {
if (hasResolved) return;
hasResolved = true;
if (code !== 0 && stderrData) {
const errorMessage = stderrData.trim();
reject(new FileDialogError(`PowerShell error: ${errorMessage}`));
return;
}
const output = stdoutData.trim();
if (isCanceled(output)) {
resolve({ files: [], canceled: true });
return;
}
const files = output.split(pathSeparator).map((s) => s.trim()).filter((s) => s !== "");
const validFiles = files.filter((f) => path.isAbsolute(f));
if (validFiles.length !== files.length) {
console.warn("Some file paths were not absolute and were filtered out");
}
resolve({ files: validFiles, canceled: false });
});
if (opts.maxTimeout) {
const timeout = setTimeout(() => {
if (!hasResolved) {
hasResolved = true;
child.kill();
reject(new FileDialogError("Dialog timeout after 5 minutes"));
}
}, opts.maxTimeout);
child.on("exit", () => {
clearTimeout(timeout);
});
}
child.stdin.write(powershellScript);
child.stdin.end();
});
}, "openWindowsFileDialog");
var openWindowsFileDialogSync = /* @__PURE__ */ __name(async (filepath, options) => {
const result = await openWindowsFileDialog(filepath, options);
return result.files;
}, "openWindowsFileDialogSync");
var open_windows_file_dialog_default = openWindowsFileDialog;
export {
open_windows_file_dialog_default as default,
openWindowsFileDialog,
openWindowsFileDialogSync
};
//# sourceMappingURL=index.mjs.map