mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 10:50:44 +00:00
Summary: - Merged fix: simplify bundled runtime dependency repair after ClawSweeper review. ClawSweeper fixups: - Included follow-up commit: fix: verify cached bundled runtime roots - Included follow-up commit: refactor: simplify plugin runtime startup paths - Included follow-up commit: refactor: trim plugin startup policy helpers - Included follow-up commit: refactor: trust package manager runtime deps materialization - Included follow-up commit: fix: narrow channel runtime deps skip policy - Included follow-up commit: refactor: defer startup plugin runtime deps - Ran the ClawSweeper repair loop before final review. Validation: - ClawSweeper review passed for head04dc566534. - Required merge gates passed before the squash merge. Prepared head SHA:04dc566534Review: https://github.com/openclaw/openclaw/pull/75183#issuecomment-4358383786 Co-authored-by: Peter Steinberger <steipete@gmail.com> Co-authored-by: Shakker <shakkerdroid@gmail.com> Co-authored-by: clawsweeper-repair <clawsweeper-repair@users.noreply.github.com>
547 lines
19 KiB
TypeScript
547 lines
19 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import type { BundledRuntimeDepsInstallParams } from "./bundled-runtime-deps-install.js";
|
|
import { withBundledRuntimeDepsFilesystemLock } from "./bundled-runtime-deps-lock.js";
|
|
import {
|
|
resolveBundledRuntimeDependencyInstallRootPlan,
|
|
resolveBundledRuntimeDependencyPackageRoot,
|
|
} from "./bundled-runtime-deps-roots.js";
|
|
import {
|
|
ensureBundledPluginRuntimeDeps,
|
|
registerBundledRuntimeDependencyNodePath,
|
|
} from "./bundled-runtime-deps.js";
|
|
import {
|
|
markBundledRuntimeDistMirrorPrepared,
|
|
shouldReusePreparedBundledRuntimeDistMirror,
|
|
} from "./bundled-runtime-dist-mirror-cache.js";
|
|
import {
|
|
materializeBundledRuntimeMirrorFile,
|
|
precomputeBundledRuntimeMirrorMetadata,
|
|
refreshBundledPluginRuntimeMirrorRoot,
|
|
type PrecomputedBundledRuntimeMirrorMetadata,
|
|
} from "./bundled-runtime-mirror.js";
|
|
|
|
const BUNDLED_RUNTIME_MIRROR_LOCK_DIR = ".openclaw-runtime-mirror.lock";
|
|
|
|
export type PreparedBundledPluginRuntimeLoadRoot = {
|
|
pluginRoot: string;
|
|
modulePath: string;
|
|
setupModulePath?: string;
|
|
};
|
|
|
|
const preparedRuntimeLoadRoots = new Map<string, PreparedBundledPluginRuntimeLoadRoot>();
|
|
|
|
function createPreparedRuntimeLoadRootKey(params: {
|
|
pluginId: string;
|
|
pluginRoot: string;
|
|
modulePath: string;
|
|
setupModulePath?: string;
|
|
env: NodeJS.ProcessEnv;
|
|
}): string {
|
|
const installRootPlan = resolveBundledRuntimeDependencyInstallRootPlan(params.pluginRoot, {
|
|
env: params.env,
|
|
});
|
|
return JSON.stringify({
|
|
pluginId: params.pluginId,
|
|
pluginRoot: path.resolve(params.pluginRoot),
|
|
modulePath: path.resolve(params.modulePath),
|
|
setupModulePath: params.setupModulePath ? path.resolve(params.setupModulePath) : "",
|
|
installRoot: path.resolve(installRootPlan.installRoot),
|
|
searchRoots: installRootPlan.searchRoots.map((root) => path.resolve(root)),
|
|
});
|
|
}
|
|
|
|
export function clearPreparedBundledPluginRuntimeLoadRoots(): void {
|
|
preparedRuntimeLoadRoots.clear();
|
|
}
|
|
|
|
function registerBundledRuntimeLoadRootAliases(params: {
|
|
pluginRoot: string;
|
|
installRoot: string;
|
|
searchRoots: readonly string[];
|
|
registerRuntimeAliasRoot?: (rootDir: string) => void;
|
|
}): void {
|
|
if (path.resolve(params.installRoot) === path.resolve(params.pluginRoot)) {
|
|
ensureOpenClawPluginSdkAlias(path.dirname(path.dirname(params.pluginRoot)));
|
|
return;
|
|
}
|
|
const packageRoot = resolveBundledRuntimeDependencyPackageRoot(params.pluginRoot);
|
|
if (packageRoot) {
|
|
registerBundledRuntimeDependencyNodePath(packageRoot);
|
|
params.registerRuntimeAliasRoot?.(packageRoot);
|
|
}
|
|
for (const searchRoot of params.searchRoots) {
|
|
registerBundledRuntimeDependencyNodePath(searchRoot);
|
|
params.registerRuntimeAliasRoot?.(searchRoot);
|
|
}
|
|
}
|
|
|
|
function formatRuntimeDepsError(error: unknown): string {
|
|
if (error instanceof Error && error.message.trim()) {
|
|
return error.message;
|
|
}
|
|
return String(error);
|
|
}
|
|
|
|
function appendPreviousRuntimeDepsRepairError(params: {
|
|
error: unknown;
|
|
previousRepairError?: unknown;
|
|
}): never {
|
|
if (params.previousRepairError === undefined) {
|
|
throw params.error;
|
|
}
|
|
throw new Error(
|
|
`${formatRuntimeDepsError(params.error)}; previous bundled runtime dependency staging failure: ${formatRuntimeDepsError(params.previousRepairError)}`,
|
|
);
|
|
}
|
|
|
|
export function isBuiltBundledPluginRuntimeRoot(pluginRoot: string): boolean {
|
|
const extensionsDir = path.dirname(pluginRoot);
|
|
const buildDir = path.dirname(extensionsDir);
|
|
return (
|
|
path.basename(extensionsDir) === "extensions" &&
|
|
(path.basename(buildDir) === "dist" || path.basename(buildDir) === "dist-runtime")
|
|
);
|
|
}
|
|
|
|
export function prepareBundledPluginRuntimeRoot(params: {
|
|
pluginId: string;
|
|
pluginRoot: string;
|
|
modulePath: string;
|
|
env?: NodeJS.ProcessEnv;
|
|
installMissingDeps?: boolean;
|
|
previousRepairError?: unknown;
|
|
logInstalled?: (installedSpecs: readonly string[]) => void;
|
|
}): { pluginRoot: string; modulePath: string } {
|
|
return prepareBundledPluginRuntimeLoadRoot(params);
|
|
}
|
|
|
|
function ensureBundledRuntimeLoadRootDeps(params: {
|
|
pluginId: string;
|
|
pluginRoot: string;
|
|
env: NodeJS.ProcessEnv;
|
|
config?: OpenClawConfig;
|
|
installMissingDeps?: boolean;
|
|
previousRepairError?: unknown;
|
|
installDeps?: (params: BundledRuntimeDepsInstallParams) => void;
|
|
logInstalled?: (installedSpecs: readonly string[]) => void;
|
|
}): void {
|
|
let depsInstallResult: ReturnType<typeof ensureBundledPluginRuntimeDeps>;
|
|
try {
|
|
depsInstallResult = ensureBundledPluginRuntimeDeps({
|
|
pluginId: params.pluginId,
|
|
pluginRoot: params.pluginRoot,
|
|
env: params.env,
|
|
config: params.config,
|
|
installMissingDeps: params.installMissingDeps,
|
|
installDeps: params.installDeps,
|
|
});
|
|
} catch (error) {
|
|
appendPreviousRuntimeDepsRepairError({
|
|
error,
|
|
previousRepairError: params.previousRepairError,
|
|
});
|
|
}
|
|
if (depsInstallResult.installedSpecs.length > 0) {
|
|
params.logInstalled?.(depsInstallResult.installedSpecs);
|
|
}
|
|
}
|
|
|
|
export function prepareBundledPluginRuntimeLoadRoot(params: {
|
|
pluginId: string;
|
|
pluginRoot: string;
|
|
modulePath: string;
|
|
setupModulePath?: string;
|
|
env?: NodeJS.ProcessEnv;
|
|
config?: OpenClawConfig;
|
|
installMissingDeps?: boolean;
|
|
previousRepairError?: unknown;
|
|
installDeps?: (params: BundledRuntimeDepsInstallParams) => void;
|
|
registerRuntimeAliasRoot?: (rootDir: string) => void;
|
|
memoizePreparedRoot?: boolean;
|
|
logInstalled?: (installedSpecs: readonly string[]) => void;
|
|
}): PreparedBundledPluginRuntimeLoadRoot {
|
|
const env = params.env ?? process.env;
|
|
const installRootPlan = resolveBundledRuntimeDependencyInstallRootPlan(params.pluginRoot, {
|
|
env,
|
|
});
|
|
const installRoot = installRootPlan.installRoot;
|
|
const cacheKey = createPreparedRuntimeLoadRootKey({ ...params, env });
|
|
const cached = params.memoizePreparedRoot ? preparedRuntimeLoadRoots.get(cacheKey) : undefined;
|
|
if (cached) {
|
|
ensureBundledRuntimeLoadRootDeps({ ...params, env });
|
|
registerBundledRuntimeLoadRootAliases({
|
|
pluginRoot: params.pluginRoot,
|
|
installRoot,
|
|
searchRoots: installRootPlan.searchRoots,
|
|
registerRuntimeAliasRoot: params.registerRuntimeAliasRoot,
|
|
});
|
|
return cached;
|
|
}
|
|
ensureBundledRuntimeLoadRootDeps({ ...params, env });
|
|
if (path.resolve(installRoot) === path.resolve(params.pluginRoot)) {
|
|
registerBundledRuntimeLoadRootAliases({
|
|
pluginRoot: params.pluginRoot,
|
|
installRoot,
|
|
searchRoots: installRootPlan.searchRoots,
|
|
registerRuntimeAliasRoot: params.registerRuntimeAliasRoot,
|
|
});
|
|
const prepared = {
|
|
pluginRoot: params.pluginRoot,
|
|
modulePath: params.modulePath,
|
|
...(params.setupModulePath ? { setupModulePath: params.setupModulePath } : {}),
|
|
};
|
|
if (params.memoizePreparedRoot) {
|
|
preparedRuntimeLoadRoots.set(cacheKey, prepared);
|
|
}
|
|
return prepared;
|
|
}
|
|
registerBundledRuntimeLoadRootAliases({
|
|
pluginRoot: params.pluginRoot,
|
|
installRoot,
|
|
searchRoots: installRootPlan.searchRoots,
|
|
registerRuntimeAliasRoot: params.registerRuntimeAliasRoot,
|
|
});
|
|
const mirrorRoot = mirrorBundledPluginRuntimeRoot({
|
|
pluginId: params.pluginId,
|
|
pluginRoot: params.pluginRoot,
|
|
installRoot,
|
|
});
|
|
const prepared = {
|
|
pluginRoot: mirrorRoot,
|
|
modulePath: remapBundledPluginRuntimePath({
|
|
source: params.modulePath,
|
|
pluginRoot: params.pluginRoot,
|
|
mirroredRoot: mirrorRoot,
|
|
}),
|
|
...(params.setupModulePath
|
|
? {
|
|
setupModulePath: remapBundledPluginRuntimePath({
|
|
source: params.setupModulePath,
|
|
pluginRoot: params.pluginRoot,
|
|
mirroredRoot: mirrorRoot,
|
|
}),
|
|
}
|
|
: {}),
|
|
};
|
|
if (params.memoizePreparedRoot) {
|
|
preparedRuntimeLoadRoots.set(cacheKey, prepared);
|
|
}
|
|
return prepared;
|
|
}
|
|
|
|
function remapBundledPluginRuntimePath(params: {
|
|
source: string;
|
|
pluginRoot: string;
|
|
mirroredRoot: string;
|
|
}): string {
|
|
const relativePath = path.relative(params.pluginRoot, params.source);
|
|
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
return params.source;
|
|
}
|
|
return path.join(params.mirroredRoot, relativePath);
|
|
}
|
|
|
|
function mirrorBundledPluginRuntimeRoot(params: {
|
|
pluginId: string;
|
|
pluginRoot: string;
|
|
installRoot: string;
|
|
}): string {
|
|
const sourceDistRoot = path.dirname(path.dirname(params.pluginRoot));
|
|
const mirrorParent = path.join(params.installRoot, path.basename(sourceDistRoot), "extensions");
|
|
const mirrorRoot = path.join(mirrorParent, params.pluginId);
|
|
const precomputedPluginRootMetadata =
|
|
path.resolve(mirrorRoot) === path.resolve(params.pluginRoot)
|
|
? undefined
|
|
: precomputeBundledRuntimeMirrorMetadata({ sourceRoot: params.pluginRoot });
|
|
const precomputedCanonicalPluginRootMetadata =
|
|
precomputeCanonicalBundledRuntimeDistPluginMetadata({
|
|
pluginRoot: params.pluginRoot,
|
|
sourceDistRoot,
|
|
});
|
|
|
|
return withBundledRuntimeDepsFilesystemLock(
|
|
params.installRoot,
|
|
BUNDLED_RUNTIME_MIRROR_LOCK_DIR,
|
|
() => {
|
|
const preparedMirrorParent = prepareBundledPluginRuntimeDistMirror({
|
|
installRoot: params.installRoot,
|
|
pluginRoot: params.pluginRoot,
|
|
precomputedCanonicalPluginRootMetadata,
|
|
});
|
|
const preparedMirrorRoot = path.join(preparedMirrorParent, 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(preparedMirrorParent, { recursive: true });
|
|
try {
|
|
fs.chmodSync(preparedMirrorParent, 0o755);
|
|
} catch {
|
|
// Best-effort only: the access check below will surface non-writable dirs.
|
|
}
|
|
fs.accessSync(preparedMirrorParent, fs.constants.W_OK);
|
|
if (path.resolve(preparedMirrorRoot) === path.resolve(params.pluginRoot)) {
|
|
return preparedMirrorRoot;
|
|
}
|
|
refreshBundledPluginRuntimeMirrorRoot({
|
|
pluginId: params.pluginId,
|
|
sourceRoot: params.pluginRoot,
|
|
targetRoot: preparedMirrorRoot,
|
|
tempDirParent: preparedMirrorParent,
|
|
precomputedSourceMetadata: precomputedPluginRootMetadata,
|
|
});
|
|
return preparedMirrorRoot;
|
|
},
|
|
);
|
|
}
|
|
|
|
function prepareBundledPluginRuntimeDistMirror(params: {
|
|
installRoot: string;
|
|
pluginRoot: string;
|
|
precomputedCanonicalPluginRootMetadata?: PrecomputedBundledRuntimeMirrorMetadata;
|
|
}): string {
|
|
const sourceExtensionsRoot = path.dirname(params.pluginRoot);
|
|
const sourceDistRoot = path.dirname(sourceExtensionsRoot);
|
|
const sourceDistRootName = path.basename(sourceDistRoot);
|
|
const mirrorDistRoot = path.join(params.installRoot, sourceDistRootName);
|
|
const mirrorExtensionsRoot = path.join(mirrorDistRoot, "extensions");
|
|
ensureBundledRuntimeMirrorDirectory(mirrorDistRoot);
|
|
fs.mkdirSync(mirrorExtensionsRoot, { recursive: true, mode: 0o755 });
|
|
ensureBundledRuntimeDistPackageJson(mirrorDistRoot);
|
|
if (!shouldReusePreparedBundledRuntimeDistMirror({ sourceDistRoot, mirrorDistRoot })) {
|
|
mirrorBundledRuntimeDistRootEntries({
|
|
sourceDistRoot,
|
|
mirrorDistRoot,
|
|
});
|
|
markBundledRuntimeDistMirrorPrepared({ sourceDistRoot, mirrorDistRoot });
|
|
}
|
|
if (sourceDistRootName === "dist-runtime") {
|
|
mirrorCanonicalBundledRuntimeDistRoot({
|
|
installRoot: params.installRoot,
|
|
pluginRoot: params.pluginRoot,
|
|
sourceRuntimeDistRoot: sourceDistRoot,
|
|
precomputedSourceMetadata: params.precomputedCanonicalPluginRootMetadata,
|
|
});
|
|
}
|
|
ensureOpenClawPluginSdkAlias(mirrorDistRoot);
|
|
return mirrorExtensionsRoot;
|
|
}
|
|
|
|
function ensureBundledRuntimeMirrorDirectory(targetRoot: string): void {
|
|
try {
|
|
const stat = fs.lstatSync(targetRoot);
|
|
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
return;
|
|
}
|
|
fs.rmSync(targetRoot, { recursive: true, force: true });
|
|
} catch (error) {
|
|
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
throw error;
|
|
}
|
|
}
|
|
fs.mkdirSync(targetRoot, { recursive: true, mode: 0o755 });
|
|
}
|
|
|
|
function isPathInsideDirectory(childPath: string, parentPath: string): boolean {
|
|
const relative = path.relative(path.resolve(parentPath), path.resolve(childPath));
|
|
return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
}
|
|
|
|
function mirrorBundledRuntimeDistRootEntries(params: {
|
|
sourceDistRoot: string;
|
|
mirrorDistRoot: string;
|
|
}): void {
|
|
const mirrorRootDirectories =
|
|
path.basename(params.sourceDistRoot) === "dist" ||
|
|
path.basename(params.sourceDistRoot) === "dist-runtime";
|
|
for (const entry of fs.readdirSync(params.sourceDistRoot, { withFileTypes: true })) {
|
|
if (entry.name === "extensions") {
|
|
continue;
|
|
}
|
|
const sourcePath = path.join(params.sourceDistRoot, entry.name);
|
|
const targetPath = path.join(params.mirrorDistRoot, entry.name);
|
|
if (path.resolve(sourcePath) === path.resolve(targetPath)) {
|
|
continue;
|
|
}
|
|
if (entry.isDirectory() && isPathInsideDirectory(targetPath, sourcePath)) {
|
|
continue;
|
|
}
|
|
const sourceStat = fs.statSync(sourcePath);
|
|
if (sourceStat.isDirectory()) {
|
|
if (!mirrorRootDirectories) {
|
|
continue;
|
|
}
|
|
refreshBundledPluginRuntimeMirrorRoot({
|
|
pluginId: `openclaw-dist:${entry.name}`,
|
|
sourceRoot: sourcePath,
|
|
targetRoot: targetPath,
|
|
tempDirParent: params.mirrorDistRoot,
|
|
});
|
|
continue;
|
|
}
|
|
if (sourceStat.isFile()) {
|
|
materializeBundledRuntimeMirrorFile(sourcePath, targetPath);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
function mirrorCanonicalBundledRuntimeDistRoot(params: {
|
|
installRoot: string;
|
|
pluginRoot: string;
|
|
sourceRuntimeDistRoot: string;
|
|
precomputedSourceMetadata?: PrecomputedBundledRuntimeMirrorMetadata;
|
|
}): void {
|
|
const sourceCanonicalDistRoot = path.join(path.dirname(params.sourceRuntimeDistRoot), "dist");
|
|
if (!fs.existsSync(sourceCanonicalDistRoot)) {
|
|
return;
|
|
}
|
|
const targetCanonicalDistRoot = path.join(params.installRoot, "dist");
|
|
ensureBundledRuntimeMirrorDirectory(targetCanonicalDistRoot);
|
|
fs.mkdirSync(path.join(targetCanonicalDistRoot, "extensions"), { recursive: true, mode: 0o755 });
|
|
ensureBundledRuntimeDistPackageJson(targetCanonicalDistRoot);
|
|
if (
|
|
!shouldReusePreparedBundledRuntimeDistMirror({
|
|
sourceDistRoot: sourceCanonicalDistRoot,
|
|
mirrorDistRoot: targetCanonicalDistRoot,
|
|
})
|
|
) {
|
|
mirrorBundledRuntimeDistRootEntries({
|
|
sourceDistRoot: sourceCanonicalDistRoot,
|
|
mirrorDistRoot: targetCanonicalDistRoot,
|
|
});
|
|
markBundledRuntimeDistMirrorPrepared({
|
|
sourceDistRoot: sourceCanonicalDistRoot,
|
|
mirrorDistRoot: targetCanonicalDistRoot,
|
|
});
|
|
}
|
|
ensureOpenClawPluginSdkAlias(targetCanonicalDistRoot);
|
|
|
|
const pluginId = path.basename(params.pluginRoot);
|
|
const sourceCanonicalPluginRoot = path.join(sourceCanonicalDistRoot, "extensions", pluginId);
|
|
if (!fs.existsSync(sourceCanonicalPluginRoot)) {
|
|
return;
|
|
}
|
|
const targetCanonicalPluginRoot = path.join(targetCanonicalDistRoot, "extensions", pluginId);
|
|
refreshBundledPluginRuntimeMirrorRoot({
|
|
pluginId,
|
|
sourceRoot: sourceCanonicalPluginRoot,
|
|
targetRoot: targetCanonicalPluginRoot,
|
|
tempDirParent: path.dirname(targetCanonicalPluginRoot),
|
|
precomputedSourceMetadata: params.precomputedSourceMetadata,
|
|
});
|
|
}
|
|
|
|
function precomputeCanonicalBundledRuntimeDistPluginMetadata(params: {
|
|
pluginRoot: string;
|
|
sourceDistRoot: string;
|
|
}): PrecomputedBundledRuntimeMirrorMetadata | undefined {
|
|
if (path.basename(params.sourceDistRoot) !== "dist-runtime") {
|
|
return undefined;
|
|
}
|
|
const pluginId = path.basename(params.pluginRoot);
|
|
const sourceCanonicalPluginRoot = path.join(
|
|
path.dirname(params.sourceDistRoot),
|
|
"dist",
|
|
"extensions",
|
|
pluginId,
|
|
);
|
|
if (!fs.existsSync(sourceCanonicalPluginRoot)) {
|
|
return undefined;
|
|
}
|
|
return precomputeBundledRuntimeMirrorMetadata({ sourceRoot: sourceCanonicalPluginRoot });
|
|
}
|
|
|
|
function ensureBundledRuntimeDistPackageJson(mirrorDistRoot: string): void {
|
|
const packageJsonPath = path.join(mirrorDistRoot, "package.json");
|
|
if (fs.existsSync(packageJsonPath)) {
|
|
return;
|
|
}
|
|
writeRuntimeJsonFile(packageJsonPath, { type: "module" });
|
|
}
|
|
|
|
function writeRuntimeJsonFile(targetPath: string, value: unknown): void {
|
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
fs.writeFileSync(targetPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
}
|
|
|
|
function hasRuntimeDefaultExport(sourcePath: string): boolean {
|
|
const text = fs.readFileSync(sourcePath, "utf8");
|
|
return /\bexport\s+default\b/u.test(text) || /\bas\s+default\b/u.test(text);
|
|
}
|
|
|
|
function writeRuntimeModuleWrapper(sourcePath: string, targetPath: string): void {
|
|
const specifier = path.relative(path.dirname(targetPath), sourcePath).replaceAll(path.sep, "/");
|
|
const normalizedSpecifier = specifier.startsWith(".") ? specifier : `./${specifier}`;
|
|
const defaultForwarder = hasRuntimeDefaultExport(sourcePath)
|
|
? [
|
|
`import defaultModule from ${JSON.stringify(normalizedSpecifier)};`,
|
|
`let defaultExport = defaultModule;`,
|
|
`for (let index = 0; index < 4 && defaultExport && typeof defaultExport === "object" && "default" in defaultExport; index += 1) {`,
|
|
` defaultExport = defaultExport.default;`,
|
|
`}`,
|
|
]
|
|
: [
|
|
`import * as module from ${JSON.stringify(normalizedSpecifier)};`,
|
|
`let defaultExport = "default" in module ? module.default : module;`,
|
|
`for (let index = 0; index < 4 && defaultExport && typeof defaultExport === "object" && "default" in defaultExport; index += 1) {`,
|
|
` 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, content, "utf8");
|
|
}
|
|
|
|
export function ensureOpenClawPluginSdkAlias(distRoot: string): void {
|
|
const pluginSdkDir = path.join(distRoot, "plugin-sdk");
|
|
if (!fs.existsSync(pluginSdkDir)) {
|
|
return;
|
|
}
|
|
|
|
const aliasDir = path.join(distRoot, "extensions", "node_modules", "openclaw");
|
|
const pluginSdkAliasDir = path.join(aliasDir, "plugin-sdk");
|
|
writeRuntimeJsonFile(path.join(aliasDir, "package.json"), {
|
|
name: "openclaw",
|
|
type: "module",
|
|
exports: {
|
|
"./plugin-sdk": "./plugin-sdk/index.js",
|
|
"./plugin-sdk/*": "./plugin-sdk/*.js",
|
|
},
|
|
});
|
|
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") {
|
|
continue;
|
|
}
|
|
writeRuntimeModuleWrapper(
|
|
path.join(pluginSdkDir, entry.name),
|
|
path.join(pluginSdkAliasDir, entry.name),
|
|
);
|
|
}
|
|
}
|