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.
205 lines (200 loc) • 7.78 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// index.ts
var open_windows_file_dialog_exports = {};
__export(open_windows_file_dialog_exports, {
default: () => open_windows_file_dialog_default,
openWindowsFileDialog: () => openWindowsFileDialog,
openWindowsFileDialogSync: () => openWindowsFileDialogSync
});
module.exports = __toCommonJS(open_windows_file_dialog_exports);
var import_node_child_process = __toESM(require("child_process"));
var import_node_path = __toESM(require("path"));
var import_node_util = __toESM(require("util"));
var execAsync = import_node_util.default.promisify(import_node_child_process.default.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 = import_node_path.default.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 = import_node_child_process.default.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) => import_node_path.default.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;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
openWindowsFileDialog,
openWindowsFileDialogSync
});
//# sourceMappingURL=index.js.map