From d3186291210fd7463455b88e21b37713b95987a5 Mon Sep 17 00:00:00 2001 From: yz <43207690+yzim@users.noreply.github.com> Date: Mon, 6 Jul 2026 22:24:02 +0800 Subject: [PATCH] Fix default bundled web UI path in packaged desktop CLI (#1899) * Fix packaged web UI dist dir resolution * Fix bundled web UI path typecheck * Fix packaged web UI path matcher * Remove duplicate packaged web UI clause * Merge packaged web UI path branch * Use bundled web UI artifact existence * Inline bundled web UI existence checks * Restore original web UI fallback branch * Reuse app-dist path in web UI resolver * Format web UI resolver tests and config * Tidy web UI resolver follow-up * fix(server): tighten bundled web UI path resolution --------- Co-authored-by: Mohamed Boudra --- .../server/src/server/config-web-ui.test.ts | 22 +---- packages/server/src/server/config.test.ts | 98 ++++++++++--------- packages/server/src/server/config.ts | 20 +++- 3 files changed, 69 insertions(+), 71 deletions(-) diff --git a/packages/server/src/server/config-web-ui.test.ts b/packages/server/src/server/config-web-ui.test.ts index 3640ff645..a92bafecb 100644 --- a/packages/server/src/server/config-web-ui.test.ts +++ b/packages/server/src/server/config-web-ui.test.ts @@ -1,13 +1,11 @@ import { mkdir, mkdtemp, writeFile, rm } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { pathToFileURL } from "node:url"; import { afterEach, describe, expect, test } from "vitest"; -import { loadConfig, resolveBundledWebUiDistDir } from "./config.js"; +import { loadConfig } from "./config.js"; const roots: string[] = []; - async function createPaseoHome(config: unknown): Promise { const root = await mkdtemp(path.join(os.tmpdir(), "paseo-config-web-ui-")); roots.push(root); @@ -49,24 +47,6 @@ describe("daemon web UI config", () => { expectBundledWebUiDistDir(config.webUi.distDir); }); - test("resolves bundled web UI dist dir from TypeScript source modules", () => { - const packageRoot = path.join(os.tmpdir(), "paseo-config-web-ui-source-package"); - const moduleUrl = pathToFileURL(path.join(packageRoot, "src", "server", "config.ts")); - - expect(resolveBundledWebUiDistDir(moduleUrl)).toBe( - path.join(packageRoot, "dist", "server", "web-ui"), - ); - }); - - test("resolves bundled web UI dist dir from compiled server modules", () => { - const packageRoot = path.join(os.tmpdir(), "paseo-config-web-ui-dist-package"); - const moduleUrl = pathToFileURL(path.join(packageRoot, "dist", "server", "config.js")); - - expect(resolveBundledWebUiDistDir(moduleUrl)).toBe( - path.join(packageRoot, "dist", "server", "web-ui"), - ); - }); - test("PASEO_WEB_UI_ENABLED overrides persisted setting", async () => { const home = await createPaseoHome({ version: 1, diff --git a/packages/server/src/server/config.test.ts b/packages/server/src/server/config.test.ts index 1708c76a1..3ae640a99 100644 --- a/packages/server/src/server/config.test.ts +++ b/packages/server/src/server/config.test.ts @@ -1,61 +1,63 @@ +import { mkdir, mkdtemp, rm } from "node:fs/promises"; +import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import { describe, expect, test } from "vitest"; +import { afterEach, describe, expect, test } from "vitest"; import { resolveBundledWebUiDistDir } from "./config.js"; -function fileUrlFor(...segments: string[]): URL { - return pathToFileURL(path.join(path.parse(process.cwd()).root, ...segments)); -} +const roots: string[] = []; describe("server config", () => { - test("resolves bundled web UI path from source-tree modules", () => { - expect( - resolveBundledWebUiDistDir( - fileUrlFor("repo", "packages", "server", "src", "server", "config.ts"), - ), - ).toBe( - path.join( - path.parse(process.cwd()).root, - "repo", - "packages", - "server", - "dist", - "server", - "web-ui", - ), - ); + afterEach(async () => { + await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); }); - test("resolves bundled web UI path from globally installed compiled modules", () => { + test("resolves bundled web UI path from source-tree modules", () => { + const root = path.parse(process.cwd()).root; expect( - resolveBundledWebUiDistDir( - fileUrlFor( - "usr", - "local", - "lib", - "node_modules", - "@getpaseo", - "server", - "dist", - "server", - "server", - "config.js", + resolveBundledWebUiDistDir({ + moduleUrl: pathToFileURL( + path.join(root, "repo", "packages", "server", "src", "server", "config.ts"), ), - ), - ).toBe( - path.join( - path.parse(process.cwd()).root, - "usr", - "local", - "lib", - "node_modules", - "@getpaseo", - "server", - "dist", - "server", - "web-ui", - ), - ); + }), + ).toBe(path.join(root, "repo", "packages", "server", "dist", "server", "web-ui")); + }); + + test("resolves bundled web UI path from globally installed compiled modules", async () => { + const packageRoot = await mkdtemp(path.join(os.tmpdir(), "paseo-config-compiled-")); + roots.push(packageRoot); + await mkdir(path.join(packageRoot, "dist", "server", "web-ui"), { recursive: true }); + + expect( + resolveBundledWebUiDistDir({ + moduleUrl: pathToFileURL(path.join(packageRoot, "dist", "server", "server", "config.js")), + }), + ).toBe(path.join(packageRoot, "dist", "server", "web-ui")); + }); + + test("resolves packaged desktop web UI path from resources app-dist", async () => { + const packageRoot = await mkdtemp(path.join(os.tmpdir(), "paseo-config-packaged-")); + roots.push(packageRoot); + await mkdir(path.join(packageRoot, "app-dist"), { recursive: true }); + + expect( + resolveBundledWebUiDistDir({ + moduleUrl: pathToFileURL( + path.join( + packageRoot, + "app.asar", + "node_modules", + "@getpaseo", + "server", + "dist", + "server", + "server", + "config.js", + ), + ), + resourcesPath: packageRoot, + }), + ).toBe(path.join(packageRoot, "app-dist")); }); }); diff --git a/packages/server/src/server/config.ts b/packages/server/src/server/config.ts index be9e68efc..83ee56267 100644 --- a/packages/server/src/server/config.ts +++ b/packages/server/src/server/config.ts @@ -1,3 +1,4 @@ +import { existsSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { resolvePaseoNodeEnv } from "./paseo-env.js"; @@ -27,7 +28,13 @@ const DEFAULT_RELAY_ENDPOINT = "relay.paseo.sh:443"; const DEFAULT_APP_BASE_URL = "https://app.paseo.sh"; const DEFAULT_TRUSTED_PROXIES = ["loopback"]; -export function resolveBundledWebUiDistDir(moduleUrl: string | URL = import.meta.url): string { +interface ResolveBundledWebUiDistDirInput { + moduleUrl?: string | URL; + resourcesPath?: string; +} + +export function resolveBundledWebUiDistDir(input: ResolveBundledWebUiDistDirInput = {}): string { + const moduleUrl = input.moduleUrl ?? import.meta.url; const moduleDir = path.dirname(fileURLToPath(moduleUrl)); if (path.basename(moduleDir) === "server" && path.basename(path.dirname(moduleDir)) === "src") { @@ -39,13 +46,22 @@ export function resolveBundledWebUiDistDir(moduleUrl: string | URL = import.meta path.basename(path.dirname(moduleDir)) === "server" && path.basename(path.dirname(path.dirname(moduleDir))) === "dist" ) { + const appDistDir = input.resourcesPath ? path.join(input.resourcesPath, "app-dist") : null; + + if (appDistDir && existsSync(appDistDir)) { + return appDistDir; + } + return path.resolve(moduleDir, "..", "web-ui"); } return path.resolve(moduleDir, "web-ui"); } -const BUNDLED_WEB_UI_DIST_DIR = resolveBundledWebUiDistDir(); +const processResourcesPath = "resourcesPath" in process ? process.resourcesPath : undefined; +const BUNDLED_WEB_UI_DIST_DIR = resolveBundledWebUiDistDir({ + resourcesPath: typeof processResourcesPath === "string" ? processResourcesPath : undefined, +}); function parseBooleanEnv(value: string | undefined): boolean | undefined { if (value === undefined) {