mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
fix(desktop): open project folders in Windows Explorer on WSL (#1581)
Route Continue in CLI directory opens through wslpath + explorer.exe on WSL Linux instead of shell.openPath, which often launches Chrome at a file:// listing via xdg-open.
This commit is contained in:
parent
a6a56099ca
commit
f9d852749f
3 changed files with 118 additions and 1 deletions
54
apps/desktop/src/main/open-project-directory.ts
Normal file
54
apps/desktop/src/main/open-project-directory.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { execFile, type ExecFileOptionsWithStringEncoding } from "node:child_process";
|
||||||
|
import os from "node:os";
|
||||||
|
import { promisify } from "node:util";
|
||||||
|
|
||||||
|
import { shell } from "electron";
|
||||||
|
|
||||||
|
const execFileAsync = promisify(execFile) as (
|
||||||
|
file: string,
|
||||||
|
args: readonly string[],
|
||||||
|
options: ExecFileOptionsWithStringEncoding,
|
||||||
|
) => Promise<{ stdout: string; stderr: string }>;
|
||||||
|
|
||||||
|
export function isWslLinux(
|
||||||
|
release = os.release(),
|
||||||
|
platform = process.platform,
|
||||||
|
): boolean {
|
||||||
|
return platform === "linux" && release.toLowerCase().includes("microsoft");
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OpenProjectDirectoryDeps = {
|
||||||
|
execFile?: typeof execFileAsync;
|
||||||
|
isWsl?: () => boolean;
|
||||||
|
openPath?: (path: string) => Promise<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a validated project directory in the host file manager. On WSL Linux,
|
||||||
|
* `shell.openPath` often delegates to Chrome via xdg-open; route through the
|
||||||
|
* Windows host Explorer instead (issue #1581).
|
||||||
|
*/
|
||||||
|
export async function openProjectDirectoryInFileManager(
|
||||||
|
resolvedDir: string,
|
||||||
|
deps: OpenProjectDirectoryDeps = {},
|
||||||
|
): Promise<string> {
|
||||||
|
const exec = deps.execFile ?? execFileAsync;
|
||||||
|
const openPath = deps.openPath ?? ((path: string) => shell.openPath(path));
|
||||||
|
const isWsl = deps.isWsl ?? (() => isWslLinux());
|
||||||
|
|
||||||
|
if (!isWsl()) {
|
||||||
|
return openPath(resolvedDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout } = await exec("wslpath", ["-w", resolvedDir], { timeout: 5000 });
|
||||||
|
const winPath = stdout.trim();
|
||||||
|
if (!winPath) {
|
||||||
|
return "wslpath returned an empty Windows path";
|
||||||
|
}
|
||||||
|
await exec("explorer.exe", [winPath], { timeout: 5000 });
|
||||||
|
return "";
|
||||||
|
} catch (err) {
|
||||||
|
return err instanceof Error ? err.message : String(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
import type { OpenDesignHostActionResult, OpenDesignHostUpdaterActionOptions } from "@open-design/host";
|
import type { OpenDesignHostActionResult, OpenDesignHostUpdaterActionOptions } from "@open-design/host";
|
||||||
|
|
||||||
import { createElectronPdfTarget, exportPdfFromHtml, savePrintReadyDocumentAsPdf } from "./pdf-export.js";
|
import { createElectronPdfTarget, exportPdfFromHtml, savePrintReadyDocumentAsPdf } from "./pdf-export.js";
|
||||||
|
import { openProjectDirectoryInFileManager } from "./open-project-directory.js";
|
||||||
import type { PrintReadyPdfOptions } from "./pdf-export.js";
|
import type { PrintReadyPdfOptions } from "./pdf-export.js";
|
||||||
import type { DesktopUpdater } from "./updater.js";
|
import type { DesktopUpdater } from "./updater.js";
|
||||||
|
|
||||||
|
|
@ -1198,7 +1199,7 @@ export async function createDesktopRuntime(options: DesktopRuntimeOptions): Prom
|
||||||
const validated = await validateExistingDirectory(resolved.context.resolvedDir);
|
const validated = await validateExistingDirectory(resolved.context.resolvedDir);
|
||||||
if (!validated.ok) return `open-path: ${validated.reason}`;
|
if (!validated.ok) return `open-path: ${validated.reason}`;
|
||||||
try {
|
try {
|
||||||
return await shell.openPath(validated.resolved);
|
return await openProjectDirectoryInFileManager(validated.resolved);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return err instanceof Error ? err.message : String(err);
|
return err instanceof Error ? err.message : String(err);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
62
apps/desktop/tests/main/open-project-directory.test.ts
Normal file
62
apps/desktop/tests/main/open-project-directory.test.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import {
|
||||||
|
isWslLinux,
|
||||||
|
openProjectDirectoryInFileManager,
|
||||||
|
} from "../../src/main/open-project-directory.js";
|
||||||
|
|
||||||
|
describe("isWslLinux", () => {
|
||||||
|
it("detects WSL kernels via microsoft in the release string", () => {
|
||||||
|
expect(isWslLinux("5.15.133.1-microsoft-standard-WSL2", "linux")).toBe(true);
|
||||||
|
expect(isWslLinux("6.8.0-45-generic", "linux")).toBe(false);
|
||||||
|
expect(isWslLinux("5.15.133.1-microsoft-standard-WSL2", "darwin")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("openProjectDirectoryInFileManager", () => {
|
||||||
|
it("uses shell.openPath on native Linux", async () => {
|
||||||
|
const openPath = vi.fn(async () => "");
|
||||||
|
const execFile = vi.fn();
|
||||||
|
|
||||||
|
const result = await openProjectDirectoryInFileManager("/tmp/project", {
|
||||||
|
isWsl: () => false,
|
||||||
|
openPath,
|
||||||
|
execFile,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe("");
|
||||||
|
expect(openPath).toHaveBeenCalledWith("/tmp/project");
|
||||||
|
expect(execFile).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("routes through wslpath and explorer.exe on WSL (#1581)", async () => {
|
||||||
|
const execFile = vi.fn(async (cmd: string, args: readonly string[]) => {
|
||||||
|
if (cmd === "wslpath") {
|
||||||
|
return { stdout: "C:\\Users\\me\\project\r\n", stderr: "" };
|
||||||
|
}
|
||||||
|
return { stdout: "", stderr: "" };
|
||||||
|
});
|
||||||
|
const openPath = vi.fn();
|
||||||
|
|
||||||
|
const result = await openProjectDirectoryInFileManager("/home/me/project", {
|
||||||
|
isWsl: () => true,
|
||||||
|
execFile,
|
||||||
|
openPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe("");
|
||||||
|
expect(execFile).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
"wslpath",
|
||||||
|
["-w", "/home/me/project"],
|
||||||
|
{ timeout: 5000 },
|
||||||
|
);
|
||||||
|
expect(execFile).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
"explorer.exe",
|
||||||
|
["C:\\Users\\me\\project"],
|
||||||
|
{ timeout: 5000 },
|
||||||
|
);
|
||||||
|
expect(openPath).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue