mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:50:42 +00:00
fix(plugins): normalize windows override imports
This commit is contained in:
@@ -93,7 +93,9 @@ GitHub-native replacement for most Parallels package/update validation, with
|
||||
Telegram proving the same package artifact through the QA live transport.
|
||||
Cross-OS release checks still cover OS-specific onboarding, installer, and
|
||||
platform behavior; package/update product validation should start with Package
|
||||
Acceptance.
|
||||
Acceptance. The Windows packaged and installer fresh lanes also verify that an
|
||||
installed package can import a browser-control override from a raw absolute
|
||||
Windows path.
|
||||
|
||||
Package Acceptance has a bounded legacy-compatibility window for already
|
||||
published packages through `2026.4.25`, including `2026.4.25-beta.*`. Those
|
||||
|
||||
@@ -566,6 +566,17 @@ async function runFreshLane(params) {
|
||||
logPath: join(params.logsDir, "fresh-install.log"),
|
||||
});
|
||||
|
||||
let browserOverrideImportStatus = "skipped";
|
||||
if (shouldRunWindowsInstalledBrowserOverrideImportSmoke()) {
|
||||
logLanePhase(lane, "windows-browser-override-import");
|
||||
browserOverrideImportStatus = await runInstalledBrowserOverrideImportSmoke({
|
||||
lane,
|
||||
env,
|
||||
prefixDir: lane.prefixDir,
|
||||
logPath: join(params.logsDir, "fresh-windows-browser-override-import.log"),
|
||||
});
|
||||
}
|
||||
|
||||
logLanePhase(lane, "onboard");
|
||||
await runOnboard({
|
||||
lane,
|
||||
@@ -617,6 +628,7 @@ async function runFreshLane(params) {
|
||||
installedCommit: installed.commit,
|
||||
dashboardStatus: "pass",
|
||||
gatewayPort: lane.gatewayPort,
|
||||
browserOverrideImportStatus,
|
||||
agentOutput: trimForSummary(agent.stdout),
|
||||
};
|
||||
} finally {
|
||||
@@ -795,6 +807,17 @@ async function runInstallerFreshSuite(params) {
|
||||
const installed = readInstalledMetadataFromCliPath(freshShell.cliPath);
|
||||
verifyInstalledCandidate(installed, params.build);
|
||||
|
||||
let browserOverrideImportStatus = "skipped";
|
||||
if (shouldRunWindowsInstalledBrowserOverrideImportSmoke()) {
|
||||
logLanePhase(lane, "windows-browser-override-import");
|
||||
browserOverrideImportStatus = await runInstalledBrowserOverrideImportSmoke({
|
||||
lane,
|
||||
env,
|
||||
prefixDir: resolveInstalledPrefixDirFromCliPath(freshShell.cliPath),
|
||||
logPath: join(params.logsDir, "installer-fresh-windows-browser-override-import.log"),
|
||||
});
|
||||
}
|
||||
|
||||
logLanePhase(lane, "onboard");
|
||||
await runOnboardWithInstalledCli({
|
||||
lane,
|
||||
@@ -900,6 +923,7 @@ async function runInstallerFreshSuite(params) {
|
||||
installedCommit: installed.commit,
|
||||
gatewayPort: lane.gatewayPort,
|
||||
dashboardStatus: "pass",
|
||||
browserOverrideImportStatus,
|
||||
discordStatus,
|
||||
agentOutput: trimForSummary(agent.stdout),
|
||||
};
|
||||
@@ -2200,6 +2224,100 @@ async function runBundledPluginPostinstall(params) {
|
||||
});
|
||||
}
|
||||
|
||||
export function shouldRunWindowsInstalledBrowserOverrideImportSmoke(platform = process.platform) {
|
||||
return platform === "win32";
|
||||
}
|
||||
|
||||
export function buildInstalledBrowserOverrideImportProbeScript() {
|
||||
return `
|
||||
import { existsSync } from "node:fs";
|
||||
import { startLazyPluginServiceModule } from "openclaw/plugin-sdk/browser-node-runtime";
|
||||
|
||||
const startedPath = process.env.OPENCLAW_BROWSER_OVERRIDE_STARTED_PATH;
|
||||
const stoppedPath = process.env.OPENCLAW_BROWSER_OVERRIDE_STOPPED_PATH;
|
||||
|
||||
if (!process.env.OPENCLAW_BROWSER_CONTROL_MODULE) {
|
||||
throw new Error("Missing OPENCLAW_BROWSER_CONTROL_MODULE.");
|
||||
}
|
||||
if (!startedPath || !stoppedPath) {
|
||||
throw new Error("Missing browser override sentinel path env.");
|
||||
}
|
||||
|
||||
const handle = await startLazyPluginServiceModule({
|
||||
overrideEnvVar: "OPENCLAW_BROWSER_CONTROL_MODULE",
|
||||
validateOverrideSpecifier: (specifier) => specifier,
|
||||
loadDefaultModule: async () => {
|
||||
throw new Error("Default browser control service should not load during override probe.");
|
||||
},
|
||||
startExportNames: ["startBrowserControlService"],
|
||||
stopExportNames: ["stopBrowserControlService"],
|
||||
});
|
||||
|
||||
if (!handle) {
|
||||
throw new Error("Browser control override probe did not return a service handle.");
|
||||
}
|
||||
if (!existsSync(startedPath)) {
|
||||
throw new Error("Browser control override start sentinel was not written.");
|
||||
}
|
||||
|
||||
await handle.stop();
|
||||
|
||||
if (!existsSync(stoppedPath)) {
|
||||
throw new Error("Browser control override stop sentinel was not written.");
|
||||
}
|
||||
|
||||
console.log("windows browser override import OK");
|
||||
`.trim();
|
||||
}
|
||||
|
||||
function buildBrowserOverrideProbeServiceModule() {
|
||||
return `
|
||||
import { writeFileSync } from "node:fs";
|
||||
|
||||
export async function startBrowserControlService() {
|
||||
writeFileSync(process.env.OPENCLAW_BROWSER_OVERRIDE_STARTED_PATH, "started\\n", "utf8");
|
||||
}
|
||||
|
||||
export async function stopBrowserControlService() {
|
||||
writeFileSync(process.env.OPENCLAW_BROWSER_OVERRIDE_STOPPED_PATH, "stopped\\n", "utf8");
|
||||
}
|
||||
`.trim();
|
||||
}
|
||||
|
||||
async function runInstalledBrowserOverrideImportSmoke(params) {
|
||||
if (!shouldRunWindowsInstalledBrowserOverrideImportSmoke()) {
|
||||
return "skipped";
|
||||
}
|
||||
|
||||
const probeDir = join(params.lane.rootDir, "browser override import probe");
|
||||
mkdirSync(probeDir, { recursive: true });
|
||||
const overridePath = join(probeDir, "browser override #module.mjs");
|
||||
const probePath = join(probeDir, "run browser override probe.mjs");
|
||||
const startedPath = join(probeDir, "started.txt");
|
||||
const stoppedPath = join(probeDir, "stopped.txt");
|
||||
|
||||
writeFileSync(overridePath, `${buildBrowserOverrideProbeServiceModule()}\n`, "utf8");
|
||||
writeFileSync(probePath, `${buildInstalledBrowserOverrideImportProbeScript()}\n`, "utf8");
|
||||
|
||||
await runCommand(process.execPath, [probePath], {
|
||||
cwd: installedPackageRoot(params.prefixDir),
|
||||
env: {
|
||||
...params.env,
|
||||
OPENCLAW_BROWSER_CONTROL_MODULE: overridePath,
|
||||
OPENCLAW_BROWSER_OVERRIDE_STARTED_PATH: startedPath,
|
||||
OPENCLAW_BROWSER_OVERRIDE_STOPPED_PATH: stoppedPath,
|
||||
},
|
||||
logPath: params.logPath,
|
||||
timeoutMs: 60_000,
|
||||
});
|
||||
|
||||
if (!existsSync(startedPath) || !existsSync(stoppedPath)) {
|
||||
throw new Error("Browser control override import probe did not write both sentinels.");
|
||||
}
|
||||
|
||||
return "pass";
|
||||
}
|
||||
|
||||
function ensureLocalNpmShim(lane) {
|
||||
const shimPath = npmShimPath(lane.prefixDir);
|
||||
if (existsSync(shimPath)) {
|
||||
|
||||
@@ -12,6 +12,9 @@ describe("toSafeImportPath", () => {
|
||||
expect(toSafeImportPath("C:\\Users\\alice\\plugin\\index.mjs")).toBe(
|
||||
"file:///C:/Users/alice/plugin/index.mjs",
|
||||
);
|
||||
expect(toSafeImportPath("C:\\Users\\alice\\plugin folder\\x#y.mjs")).toBe(
|
||||
"file:///C:/Users/alice/plugin%20folder/x%23y.mjs",
|
||||
);
|
||||
expect(toSafeImportPath("\\\\server\\share\\plugin\\index.mjs")).toBe(
|
||||
"file://server/share/plugin/index.mjs",
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
/**
|
||||
* On Windows, Node's ESM loader requires absolute paths to be expressed as
|
||||
@@ -12,11 +13,7 @@ export function toSafeImportPath(specifier: string): string {
|
||||
return specifier;
|
||||
}
|
||||
if (path.win32.isAbsolute(specifier)) {
|
||||
const normalizedSpecifier = specifier.replaceAll("\\", "/");
|
||||
if (normalizedSpecifier.startsWith("//")) {
|
||||
return new URL(`file:${encodeURI(normalizedSpecifier)}`).href;
|
||||
}
|
||||
return new URL(`file:///${encodeURI(normalizedSpecifier)}`).href;
|
||||
return pathToFileURL(specifier, { windows: true }).href;
|
||||
}
|
||||
return specifier;
|
||||
}
|
||||
|
||||
@@ -95,12 +95,14 @@ describe("startLazyPluginServiceModule", () => {
|
||||
const importModule = vi.fn(async () => ({ startOverride: start }));
|
||||
|
||||
try {
|
||||
await defaultLoadOverrideModule("C:\\Users\\alice\\browser-service.mjs", importModule);
|
||||
await defaultLoadOverrideModule("C:\\Users\\alice\\plugin folder\\x#y.mjs", importModule);
|
||||
} finally {
|
||||
platformSpy.mockRestore();
|
||||
}
|
||||
|
||||
expect(importModule).toHaveBeenCalledWith("file:///C:/Users/alice/browser-service.mjs");
|
||||
expect(importModule).toHaveBeenCalledWith(
|
||||
"file:///C:/Users/alice/plugin%20folder/x%23y.mjs",
|
||||
);
|
||||
});
|
||||
|
||||
it("leaves caller-supplied override loaders responsible for their own specifiers", async () => {
|
||||
|
||||
@@ -7338,6 +7338,9 @@ export const runtimeValue = helperValue;`,
|
||||
expect(__testing.toSafeImportPath("C:\\Users\\alice\\plugin\\index.mjs")).toBe(
|
||||
"file:///C:/Users/alice/plugin/index.mjs",
|
||||
);
|
||||
expect(__testing.toSafeImportPath("C:\\Users\\alice\\plugin folder\\x#y.mjs")).toBe(
|
||||
"file:///C:/Users/alice/plugin%20folder/x%23y.mjs",
|
||||
);
|
||||
expect(__testing.toSafeImportPath("\\\\server\\share\\plugin\\index.mjs")).toBe(
|
||||
"file://server/share/plugin/index.mjs",
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
agentOutputHasExpectedOkMarker,
|
||||
buildWindowsDevUpdateToolchainCheckScript,
|
||||
buildWindowsFreshShellVersionCheckScript,
|
||||
buildInstalledBrowserOverrideImportProbeScript,
|
||||
buildWindowsPathBootstrapScript,
|
||||
canConnectToLoopbackPort,
|
||||
buildDiscordSmokeGuildsConfig,
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
resolveRunnerMatrix,
|
||||
resolveStaticFileContentType,
|
||||
shouldExerciseManagedGatewayLifecycleAfterInstall,
|
||||
shouldRunWindowsInstalledBrowserOverrideImportSmoke,
|
||||
shouldSkipInstallerDaemonHealthCheck,
|
||||
shouldStopManagedGatewayBeforeManualFallback,
|
||||
shouldRunMainChannelDevUpdate,
|
||||
@@ -289,6 +291,19 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
|
||||
expect(shouldSkipInstallerDaemonHealthCheck("linux")).toBe(false);
|
||||
});
|
||||
|
||||
it("runs the installed browser override import smoke only on native Windows", () => {
|
||||
expect(shouldRunWindowsInstalledBrowserOverrideImportSmoke("win32")).toBe(true);
|
||||
expect(shouldRunWindowsInstalledBrowserOverrideImportSmoke("darwin")).toBe(false);
|
||||
expect(shouldRunWindowsInstalledBrowserOverrideImportSmoke("linux")).toBe(false);
|
||||
|
||||
const script = buildInstalledBrowserOverrideImportProbeScript();
|
||||
expect(script).toContain('from "openclaw/plugin-sdk/browser-node-runtime"');
|
||||
expect(script).toContain('overrideEnvVar: "OPENCLAW_BROWSER_CONTROL_MODULE"');
|
||||
expect(script).toContain("startBrowserControlService");
|
||||
expect(script).toContain("stopBrowserControlService");
|
||||
expect(script).toContain("Browser control override start sentinel was not written.");
|
||||
});
|
||||
|
||||
it("normalizes Windows installed CLI paths to the cmd shim", () => {
|
||||
expect(
|
||||
normalizeWindowsInstalledCliPath(
|
||||
|
||||
Reference in New Issue
Block a user