mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:00:43 +00:00
fix(plugins): serialize bundled runtime mirrors
This commit is contained in:
@@ -1526,7 +1526,7 @@ if [ -z "\$dashboard_port" ] || [ "\$dashboard_port" = "\$dashboard_http_url" ];
|
||||
echo "failed to parse dashboard port from \$dashboard_http_url" >&2
|
||||
exit 1
|
||||
fi
|
||||
deadline=\$((SECONDS + 30))
|
||||
deadline=\$((SECONDS + 120))
|
||||
dashboard_ready=0
|
||||
while [ \$SECONDS -lt \$deadline ]; do
|
||||
if curl -fsSL --connect-timeout 2 --max-time 5 "\$dashboard_http_url" >/tmp/openclaw-dashboard-smoke.html 2>/dev/null; then
|
||||
|
||||
@@ -341,9 +341,13 @@ function removeRuntimeDepsLockIfStale(lockDir: string, nowMs: number): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
function withBundledRuntimeDepsInstallRootLock<T>(installRoot: string, run: () => T): T {
|
||||
export function withBundledRuntimeDepsFilesystemLock<T>(
|
||||
installRoot: string,
|
||||
lockName: string,
|
||||
run: () => T,
|
||||
): T {
|
||||
fs.mkdirSync(installRoot, { recursive: true });
|
||||
const lockDir = path.join(installRoot, BUNDLED_RUNTIME_DEPS_LOCK_DIR);
|
||||
const lockDir = path.join(installRoot, lockName);
|
||||
const startedAt = Date.now();
|
||||
let locked = false;
|
||||
while (!locked) {
|
||||
@@ -390,6 +394,10 @@ function withBundledRuntimeDepsInstallRootLock<T>(installRoot: string, run: () =
|
||||
}
|
||||
}
|
||||
|
||||
function withBundledRuntimeDepsInstallRootLock<T>(installRoot: string, run: () => T): T {
|
||||
return withBundledRuntimeDepsFilesystemLock(installRoot, BUNDLED_RUNTIME_DEPS_LOCK_DIR, run);
|
||||
}
|
||||
|
||||
function collectRuntimeDeps(packageJson: JsonObject): Record<string, unknown> {
|
||||
return {
|
||||
...(packageJson.dependencies as Record<string, unknown> | undefined),
|
||||
|
||||
@@ -5,9 +5,11 @@ import {
|
||||
resolveBundledRuntimeDependencyInstallRoot,
|
||||
resolveBundledRuntimeDependencyPackageRoot,
|
||||
registerBundledRuntimeDependencyNodePath,
|
||||
withBundledRuntimeDepsFilesystemLock,
|
||||
} from "./bundled-runtime-deps.js";
|
||||
|
||||
const bundledRuntimeDepsRetainSpecsByInstallRoot = new Map<string, readonly string[]>();
|
||||
const BUNDLED_RUNTIME_MIRROR_LOCK_DIR = ".openclaw-runtime-mirror.lock";
|
||||
|
||||
export function isBuiltBundledPluginRuntimeRoot(pluginRoot: string): boolean {
|
||||
const extensionsDir = path.dirname(pluginRoot);
|
||||
@@ -83,34 +85,40 @@ function mirrorBundledPluginRuntimeRoot(params: {
|
||||
pluginRoot: string;
|
||||
installRoot: string;
|
||||
}): string {
|
||||
const mirrorParent = prepareBundledPluginRuntimeDistMirror({
|
||||
installRoot: params.installRoot,
|
||||
pluginRoot: params.pluginRoot,
|
||||
});
|
||||
const mirrorRoot = path.join(mirrorParent, params.pluginId);
|
||||
fs.mkdirSync(params.installRoot, { recursive: true });
|
||||
try {
|
||||
fs.chmodSync(params.installRoot, 0o755);
|
||||
} catch {
|
||||
// Best-effort only: staged roots may live on filesystems that reject chmod.
|
||||
}
|
||||
fs.mkdirSync(mirrorParent, { recursive: true });
|
||||
try {
|
||||
fs.chmodSync(mirrorParent, 0o755);
|
||||
} catch {
|
||||
// Best-effort only: the access check below will surface non-writable dirs.
|
||||
}
|
||||
fs.accessSync(mirrorParent, fs.constants.W_OK);
|
||||
const tempDir = fs.mkdtempSync(path.join(mirrorParent, `.plugin-${params.pluginId}-`));
|
||||
const stagedRoot = path.join(tempDir, "plugin");
|
||||
try {
|
||||
copyBundledPluginRuntimeRoot(params.pluginRoot, stagedRoot);
|
||||
fs.rmSync(mirrorRoot, { recursive: true, force: true });
|
||||
fs.renameSync(stagedRoot, mirrorRoot);
|
||||
} finally {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
return mirrorRoot;
|
||||
return withBundledRuntimeDepsFilesystemLock(
|
||||
params.installRoot,
|
||||
BUNDLED_RUNTIME_MIRROR_LOCK_DIR,
|
||||
() => {
|
||||
const mirrorParent = prepareBundledPluginRuntimeDistMirror({
|
||||
installRoot: params.installRoot,
|
||||
pluginRoot: params.pluginRoot,
|
||||
});
|
||||
const mirrorRoot = path.join(mirrorParent, params.pluginId);
|
||||
fs.mkdirSync(params.installRoot, { recursive: true });
|
||||
try {
|
||||
fs.chmodSync(params.installRoot, 0o755);
|
||||
} catch {
|
||||
// Best-effort only: staged roots may live on filesystems that reject chmod.
|
||||
}
|
||||
fs.mkdirSync(mirrorParent, { recursive: true });
|
||||
try {
|
||||
fs.chmodSync(mirrorParent, 0o755);
|
||||
} catch {
|
||||
// Best-effort only: the access check below will surface non-writable dirs.
|
||||
}
|
||||
fs.accessSync(mirrorParent, fs.constants.W_OK);
|
||||
const tempDir = fs.mkdtempSync(path.join(mirrorParent, `.plugin-${params.pluginId}-`));
|
||||
const stagedRoot = path.join(tempDir, "plugin");
|
||||
try {
|
||||
copyBundledPluginRuntimeRoot(params.pluginRoot, stagedRoot);
|
||||
fs.rmSync(mirrorRoot, { recursive: true, force: true });
|
||||
fs.renameSync(stagedRoot, mirrorRoot);
|
||||
} finally {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
return mirrorRoot;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function prepareBundledPluginRuntimeDistMirror(params: {
|
||||
@@ -135,6 +143,9 @@ function prepareBundledPluginRuntimeDistMirror(params: {
|
||||
try {
|
||||
fs.symlinkSync(sourcePath, targetPath, entry.isDirectory() ? "junction" : "file");
|
||||
} catch {
|
||||
if (fs.existsSync(targetPath)) {
|
||||
continue;
|
||||
}
|
||||
if (entry.isDirectory()) {
|
||||
copyBundledPluginRuntimeRoot(sourcePath, targetPath);
|
||||
} else if (entry.isFile()) {
|
||||
@@ -211,17 +222,21 @@ function writeRuntimeModuleWrapper(sourcePath: string, targetPath: string): void
|
||||
` defaultExport = defaultExport.default;`,
|
||||
`}`,
|
||||
];
|
||||
const content = [
|
||||
`export * from ${JSON.stringify(normalizedSpecifier)};`,
|
||||
...defaultForwarder,
|
||||
"export { defaultExport as default };",
|
||||
"",
|
||||
].join("\n");
|
||||
try {
|
||||
if (fs.readFileSync(targetPath, "utf8") === content) {
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Missing or unreadable wrapper; rewrite below.
|
||||
}
|
||||
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
targetPath,
|
||||
[
|
||||
`export * from ${JSON.stringify(normalizedSpecifier)};`,
|
||||
...defaultForwarder,
|
||||
"export { defaultExport as default };",
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(targetPath, content, "utf8");
|
||||
}
|
||||
|
||||
function ensureOpenClawPluginSdkAlias(distRoot: string): void {
|
||||
@@ -240,7 +255,14 @@ function ensureOpenClawPluginSdkAlias(distRoot: string): void {
|
||||
"./plugin-sdk/*": "./plugin-sdk/*.js",
|
||||
},
|
||||
});
|
||||
fs.rmSync(pluginSdkAliasDir, { recursive: true, force: true });
|
||||
try {
|
||||
if (fs.existsSync(pluginSdkAliasDir) && !fs.lstatSync(pluginSdkAliasDir).isDirectory()) {
|
||||
fs.rmSync(pluginSdkAliasDir, { recursive: true, force: true });
|
||||
}
|
||||
} catch {
|
||||
// Another process may be creating the alias at the same time; mkdir/write
|
||||
// below will either converge or surface the real filesystem error.
|
||||
}
|
||||
fs.mkdirSync(pluginSdkAliasDir, { recursive: true });
|
||||
for (const entry of fs.readdirSync(pluginSdkDir, { withFileTypes: true })) {
|
||||
if (!entry.isFile() || path.extname(entry.name) !== ".js") {
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
resolveBundledRuntimeDependencyInstallRoot,
|
||||
resolveBundledRuntimeDependencyPackageRoot,
|
||||
registerBundledRuntimeDependencyNodePath,
|
||||
withBundledRuntimeDepsFilesystemLock,
|
||||
type BundledRuntimeDepsInstallParams,
|
||||
} from "./bundled-runtime-deps.js";
|
||||
import {
|
||||
@@ -269,6 +270,7 @@ export function clearPluginLoaderCache(): void {
|
||||
}
|
||||
|
||||
const defaultLogger = () => createSubsystemLogger("plugins");
|
||||
const BUNDLED_RUNTIME_MIRROR_LOCK_DIR = ".openclaw-runtime-mirror.lock";
|
||||
|
||||
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
||||
return (
|
||||
@@ -706,34 +708,40 @@ function mirrorBundledPluginRuntimeRoot(params: {
|
||||
pluginRoot: string;
|
||||
installRoot: string;
|
||||
}): string {
|
||||
const mirrorParent = prepareBundledPluginRuntimeDistMirror({
|
||||
installRoot: params.installRoot,
|
||||
pluginRoot: params.pluginRoot,
|
||||
});
|
||||
const mirrorRoot = path.join(mirrorParent, params.pluginId);
|
||||
fs.mkdirSync(params.installRoot, { recursive: true });
|
||||
try {
|
||||
fs.chmodSync(params.installRoot, 0o755);
|
||||
} catch {
|
||||
// Best-effort only: staged roots may live on filesystems that reject chmod.
|
||||
}
|
||||
fs.mkdirSync(mirrorParent, { recursive: true });
|
||||
try {
|
||||
fs.chmodSync(mirrorParent, 0o755);
|
||||
} catch {
|
||||
// Best-effort only: the access check below will surface non-writable dirs.
|
||||
}
|
||||
fs.accessSync(mirrorParent, fs.constants.W_OK);
|
||||
const tempDir = fs.mkdtempSync(path.join(mirrorParent, `.plugin-${params.pluginId}-`));
|
||||
const stagedRoot = path.join(tempDir, "plugin");
|
||||
try {
|
||||
copyBundledPluginRuntimeRoot(params.pluginRoot, stagedRoot);
|
||||
fs.rmSync(mirrorRoot, { recursive: true, force: true });
|
||||
fs.renameSync(stagedRoot, mirrorRoot);
|
||||
} finally {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
return mirrorRoot;
|
||||
return withBundledRuntimeDepsFilesystemLock(
|
||||
params.installRoot,
|
||||
BUNDLED_RUNTIME_MIRROR_LOCK_DIR,
|
||||
() => {
|
||||
const mirrorParent = prepareBundledPluginRuntimeDistMirror({
|
||||
installRoot: params.installRoot,
|
||||
pluginRoot: params.pluginRoot,
|
||||
});
|
||||
const mirrorRoot = path.join(mirrorParent, params.pluginId);
|
||||
fs.mkdirSync(params.installRoot, { recursive: true });
|
||||
try {
|
||||
fs.chmodSync(params.installRoot, 0o755);
|
||||
} catch {
|
||||
// Best-effort only: staged roots may live on filesystems that reject chmod.
|
||||
}
|
||||
fs.mkdirSync(mirrorParent, { recursive: true });
|
||||
try {
|
||||
fs.chmodSync(mirrorParent, 0o755);
|
||||
} catch {
|
||||
// Best-effort only: the access check below will surface non-writable dirs.
|
||||
}
|
||||
fs.accessSync(mirrorParent, fs.constants.W_OK);
|
||||
const tempDir = fs.mkdtempSync(path.join(mirrorParent, `.plugin-${params.pluginId}-`));
|
||||
const stagedRoot = path.join(tempDir, "plugin");
|
||||
try {
|
||||
copyBundledPluginRuntimeRoot(params.pluginRoot, stagedRoot);
|
||||
fs.rmSync(mirrorRoot, { recursive: true, force: true });
|
||||
fs.renameSync(stagedRoot, mirrorRoot);
|
||||
} finally {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
return mirrorRoot;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function prepareBundledPluginRuntimeDistMirror(params: {
|
||||
@@ -759,6 +767,9 @@ function prepareBundledPluginRuntimeDistMirror(params: {
|
||||
try {
|
||||
fs.symlinkSync(sourcePath, targetPath, entry.isDirectory() ? "junction" : "file");
|
||||
} catch {
|
||||
if (fs.existsSync(targetPath)) {
|
||||
continue;
|
||||
}
|
||||
if (entry.isDirectory()) {
|
||||
copyBundledPluginRuntimeRoot(sourcePath, targetPath);
|
||||
} else if (entry.isFile()) {
|
||||
@@ -853,17 +864,21 @@ function writeRuntimeModuleWrapper(sourcePath: string, targetPath: string): void
|
||||
` defaultExport = defaultExport.default;`,
|
||||
`}`,
|
||||
];
|
||||
const content = [
|
||||
`export * from ${JSON.stringify(normalizedSpecifier)};`,
|
||||
...defaultForwarder,
|
||||
"export { defaultExport as default };",
|
||||
"",
|
||||
].join("\n");
|
||||
try {
|
||||
if (fs.readFileSync(targetPath, "utf8") === content) {
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Missing or unreadable wrapper; rewrite below.
|
||||
}
|
||||
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
targetPath,
|
||||
[
|
||||
`export * from ${JSON.stringify(normalizedSpecifier)};`,
|
||||
...defaultForwarder,
|
||||
"export { defaultExport as default };",
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(targetPath, content, "utf8");
|
||||
}
|
||||
|
||||
function ensureOpenClawPluginSdkAlias(distRoot: string): void {
|
||||
|
||||
Reference in New Issue
Block a user