mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 21:21:24 +00:00
fix(ui): fix web UI after tsdown migration and typing changes
This commit is contained in:
@@ -2,7 +2,12 @@ import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveControlUiDistIndexPath, resolveControlUiRepoRoot } from "./control-ui-assets.js";
|
||||
import {
|
||||
resolveControlUiDistIndexPath,
|
||||
resolveControlUiRepoRoot,
|
||||
resolveControlUiRootOverrideSync,
|
||||
resolveControlUiRootSync,
|
||||
} from "./control-ui-assets.js";
|
||||
|
||||
describe("control UI assets helpers", () => {
|
||||
it("resolves repo root from src argv1", async () => {
|
||||
@@ -43,6 +48,53 @@ describe("control UI assets helpers", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves control-ui root for dist bundle argv1", async () => {
|
||||
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-"));
|
||||
try {
|
||||
await fs.mkdir(path.join(tmp, "dist", "control-ui"), { recursive: true });
|
||||
await fs.writeFile(path.join(tmp, "dist", "bundle.js"), "export {};\n");
|
||||
await fs.writeFile(path.join(tmp, "dist", "control-ui", "index.html"), "<html></html>\n");
|
||||
|
||||
expect(resolveControlUiRootSync({ argv1: path.join(tmp, "dist", "bundle.js") })).toBe(
|
||||
path.join(tmp, "dist", "control-ui"),
|
||||
);
|
||||
} finally {
|
||||
await fs.rm(tmp, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves control-ui root for dist/gateway bundle argv1", async () => {
|
||||
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-"));
|
||||
try {
|
||||
await fs.writeFile(path.join(tmp, "package.json"), JSON.stringify({ name: "openclaw" }));
|
||||
await fs.mkdir(path.join(tmp, "dist", "gateway"), { recursive: true });
|
||||
await fs.mkdir(path.join(tmp, "dist", "control-ui"), { recursive: true });
|
||||
await fs.writeFile(path.join(tmp, "dist", "gateway", "control-ui.js"), "export {};\n");
|
||||
await fs.writeFile(path.join(tmp, "dist", "control-ui", "index.html"), "<html></html>\n");
|
||||
|
||||
expect(
|
||||
resolveControlUiRootSync({ argv1: path.join(tmp, "dist", "gateway", "control-ui.js") }),
|
||||
).toBe(path.join(tmp, "dist", "control-ui"));
|
||||
} finally {
|
||||
await fs.rm(tmp, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves control-ui root from override directory or index.html", async () => {
|
||||
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-"));
|
||||
try {
|
||||
const uiDir = path.join(tmp, "dist", "control-ui");
|
||||
await fs.mkdir(uiDir, { recursive: true });
|
||||
await fs.writeFile(path.join(uiDir, "index.html"), "<html></html>\n");
|
||||
|
||||
expect(resolveControlUiRootOverrideSync(uiDir)).toBe(uiDir);
|
||||
expect(resolveControlUiRootOverrideSync(path.join(uiDir, "index.html"))).toBe(uiDir);
|
||||
expect(resolveControlUiRootOverrideSync(path.join(uiDir, "missing.html"))).toBeNull();
|
||||
} finally {
|
||||
await fs.rm(tmp, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves dist control-ui index path from package root argv1", async () => {
|
||||
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-"));
|
||||
try {
|
||||
@@ -59,6 +111,22 @@ describe("control UI assets helpers", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves control-ui root for package entrypoint argv1", async () => {
|
||||
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-"));
|
||||
try {
|
||||
await fs.writeFile(path.join(tmp, "package.json"), JSON.stringify({ name: "openclaw" }));
|
||||
await fs.writeFile(path.join(tmp, "openclaw.mjs"), "export {};\n");
|
||||
await fs.mkdir(path.join(tmp, "dist", "control-ui"), { recursive: true });
|
||||
await fs.writeFile(path.join(tmp, "dist", "control-ui", "index.html"), "<html></html>\n");
|
||||
|
||||
expect(resolveControlUiRootSync({ argv1: path.join(tmp, "openclaw.mjs") })).toBe(
|
||||
path.join(tmp, "dist", "control-ui"),
|
||||
);
|
||||
} finally {
|
||||
await fs.rm(tmp, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves dist control-ui index path from .bin argv1", async () => {
|
||||
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-"));
|
||||
try {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { runCommandWithTimeout } from "../process/exec.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||
import { resolveOpenClawPackageRoot } from "./openclaw-root.js";
|
||||
import { resolveOpenClawPackageRoot, resolveOpenClawPackageRootSync } from "./openclaw-root.js";
|
||||
|
||||
export function resolveControlUiRepoRoot(
|
||||
argv1: string | undefined = process.argv[1],
|
||||
@@ -59,6 +60,86 @@ export async function resolveControlUiDistIndexPath(
|
||||
return path.join(packageRoot, "dist", "control-ui", "index.html");
|
||||
}
|
||||
|
||||
export type ControlUiRootResolveOptions = {
|
||||
argv1?: string;
|
||||
moduleUrl?: string;
|
||||
cwd?: string;
|
||||
execPath?: string;
|
||||
};
|
||||
|
||||
function addCandidate(candidates: Set<string>, value: string | null) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
candidates.add(path.resolve(value));
|
||||
}
|
||||
|
||||
export function resolveControlUiRootOverrideSync(rootOverride: string): string | null {
|
||||
const resolved = path.resolve(rootOverride);
|
||||
try {
|
||||
const stats = fs.statSync(resolved);
|
||||
if (stats.isFile()) {
|
||||
return path.basename(resolved) === "index.html" ? path.dirname(resolved) : null;
|
||||
}
|
||||
if (stats.isDirectory()) {
|
||||
const indexPath = path.join(resolved, "index.html");
|
||||
return fs.existsSync(indexPath) ? resolved : null;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resolveControlUiRootSync(opts: ControlUiRootResolveOptions = {}): string | null {
|
||||
const candidates = new Set<string>();
|
||||
const argv1 = opts.argv1 ?? process.argv[1];
|
||||
const cwd = opts.cwd ?? process.cwd();
|
||||
const moduleDir = opts.moduleUrl ? path.dirname(fileURLToPath(opts.moduleUrl)) : null;
|
||||
const argv1Dir = argv1 ? path.dirname(path.resolve(argv1)) : null;
|
||||
const execDir = (() => {
|
||||
try {
|
||||
const execPath = opts.execPath ?? process.execPath;
|
||||
return path.dirname(fs.realpathSync(execPath));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
const packageRoot = resolveOpenClawPackageRootSync({
|
||||
argv1,
|
||||
moduleUrl: opts.moduleUrl,
|
||||
cwd,
|
||||
});
|
||||
|
||||
// Packaged app: control-ui lives alongside the executable.
|
||||
addCandidate(candidates, execDir ? path.join(execDir, "control-ui") : null);
|
||||
if (moduleDir) {
|
||||
// dist/<bundle>.js -> dist/control-ui
|
||||
addCandidate(candidates, path.join(moduleDir, "control-ui"));
|
||||
// dist/gateway/control-ui.js -> dist/control-ui
|
||||
addCandidate(candidates, path.join(moduleDir, "../control-ui"));
|
||||
// src/gateway/control-ui.ts -> dist/control-ui
|
||||
addCandidate(candidates, path.join(moduleDir, "../../dist/control-ui"));
|
||||
}
|
||||
if (argv1Dir) {
|
||||
// openclaw.mjs or dist/<bundle>.js
|
||||
addCandidate(candidates, path.join(argv1Dir, "dist", "control-ui"));
|
||||
addCandidate(candidates, path.join(argv1Dir, "control-ui"));
|
||||
}
|
||||
if (packageRoot) {
|
||||
addCandidate(candidates, path.join(packageRoot, "dist", "control-ui"));
|
||||
}
|
||||
addCandidate(candidates, path.join(cwd, "dist", "control-ui"));
|
||||
|
||||
for (const dir of candidates) {
|
||||
const indexPath = path.join(dir, "index.html");
|
||||
if (fs.existsSync(indexPath)) {
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export type EnsureControlUiAssetsResult = {
|
||||
ok: boolean;
|
||||
built: boolean;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import fsSync from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -14,6 +15,16 @@ async function readPackageName(dir: string): Promise<string | null> {
|
||||
}
|
||||
}
|
||||
|
||||
function readPackageNameSync(dir: string): string | null {
|
||||
try {
|
||||
const raw = fsSync.readFileSync(path.join(dir, "package.json"), "utf-8");
|
||||
const parsed = JSON.parse(raw) as { name?: unknown };
|
||||
return typeof parsed.name === "string" ? parsed.name : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function findPackageRoot(startDir: string, maxDepth = 12): Promise<string | null> {
|
||||
let current = path.resolve(startDir);
|
||||
for (let i = 0; i < maxDepth; i += 1) {
|
||||
@@ -30,6 +41,22 @@ async function findPackageRoot(startDir: string, maxDepth = 12): Promise<string
|
||||
return null;
|
||||
}
|
||||
|
||||
function findPackageRootSync(startDir: string, maxDepth = 12): string | null {
|
||||
let current = path.resolve(startDir);
|
||||
for (let i = 0; i < maxDepth; i += 1) {
|
||||
const name = readPackageNameSync(current);
|
||||
if (name && CORE_PACKAGE_NAMES.has(name)) {
|
||||
return current;
|
||||
}
|
||||
const parent = path.dirname(current);
|
||||
if (parent === current) {
|
||||
break;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function candidateDirsFromArgv1(argv1: string): string[] {
|
||||
const normalized = path.resolve(argv1);
|
||||
const candidates = [path.dirname(normalized)];
|
||||
@@ -69,3 +96,30 @@ export async function resolveOpenClawPackageRoot(opts: {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resolveOpenClawPackageRootSync(opts: {
|
||||
cwd?: string;
|
||||
argv1?: string;
|
||||
moduleUrl?: string;
|
||||
}): string | null {
|
||||
const candidates: string[] = [];
|
||||
|
||||
if (opts.moduleUrl) {
|
||||
candidates.push(path.dirname(fileURLToPath(opts.moduleUrl)));
|
||||
}
|
||||
if (opts.argv1) {
|
||||
candidates.push(...candidateDirsFromArgv1(opts.argv1));
|
||||
}
|
||||
if (opts.cwd) {
|
||||
candidates.push(opts.cwd);
|
||||
}
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const found = findPackageRootSync(candidate);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user