mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:30:43 +00:00
refactor(plugins): split bundled runtime deps jiti aliases
This commit is contained in:
208
src/plugins/bundled-runtime-deps-jiti-aliases.ts
Normal file
208
src/plugins/bundled-runtime-deps-jiti-aliases.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { isPathInside, safeStatSync } from "./path-safety.js";
|
||||
import { normalizeJitiAliasTargetPath } from "./sdk-alias.js";
|
||||
|
||||
type RuntimeDependencyPackageJson = {
|
||||
dependencies?: Record<string, unknown>;
|
||||
optionalDependencies?: Record<string, unknown>;
|
||||
peerDependencies?: Record<string, unknown>;
|
||||
exports?: unknown;
|
||||
module?: string;
|
||||
main?: string;
|
||||
};
|
||||
|
||||
const bundledRuntimeDependencyJitiAliases = new Map<string, string>();
|
||||
|
||||
function readRuntimeDependencyPackageJson(
|
||||
packageJsonPath: string,
|
||||
): RuntimeDependencyPackageJson | null {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) as RuntimeDependencyPackageJson;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function collectRuntimeDependencyNames(pkg: RuntimeDependencyPackageJson): string[] {
|
||||
return [
|
||||
...Object.keys(pkg.dependencies ?? {}),
|
||||
...Object.keys(pkg.optionalDependencies ?? {}),
|
||||
...Object.keys(pkg.peerDependencies ?? {}),
|
||||
].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function resolveRuntimePackageImportTarget(exportsField: unknown): string | null {
|
||||
if (typeof exportsField === "string") {
|
||||
return exportsField;
|
||||
}
|
||||
if (Array.isArray(exportsField)) {
|
||||
for (const entry of exportsField) {
|
||||
const resolved = resolveRuntimePackageImportTarget(entry);
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (!exportsField || typeof exportsField !== "object" || Array.isArray(exportsField)) {
|
||||
return null;
|
||||
}
|
||||
const record = exportsField as Record<string, unknown>;
|
||||
if (Object.prototype.hasOwnProperty.call(record, ".")) {
|
||||
return resolveRuntimePackageImportTarget(record["."]);
|
||||
}
|
||||
for (const condition of ["import", "node", "default"] as const) {
|
||||
const resolved = resolveRuntimePackageImportTarget(record[condition]);
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectRuntimePackageWildcardImportTargets(
|
||||
dependencyRoot: string,
|
||||
exportKey: string,
|
||||
targetPattern: string,
|
||||
): Map<string, string> {
|
||||
const targets = new Map<string, string>();
|
||||
const wildcardIndex = exportKey.indexOf("*");
|
||||
const targetWildcardIndex = targetPattern.indexOf("*");
|
||||
if (wildcardIndex === -1 || targetWildcardIndex === -1) {
|
||||
return targets;
|
||||
}
|
||||
const exportPrefix = exportKey.slice(0, wildcardIndex);
|
||||
const exportSuffix = exportKey.slice(wildcardIndex + 1);
|
||||
const targetPrefix = targetPattern.slice(0, targetWildcardIndex);
|
||||
const targetSuffix = targetPattern.slice(targetWildcardIndex + 1);
|
||||
const targetBase = path.resolve(dependencyRoot, targetPrefix);
|
||||
if (!isPathInside(dependencyRoot, targetBase) || !safeStatSync(targetBase)?.isDirectory()) {
|
||||
return targets;
|
||||
}
|
||||
const stack = [targetBase];
|
||||
while (stack.length > 0) {
|
||||
const currentDir = stack.pop();
|
||||
if (!currentDir) {
|
||||
continue;
|
||||
}
|
||||
let entries: fs.Dirent[];
|
||||
try {
|
||||
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(currentDir, entry.name);
|
||||
if (!isPathInside(dependencyRoot, entryPath)) {
|
||||
continue;
|
||||
}
|
||||
if (entry.isDirectory()) {
|
||||
stack.push(entryPath);
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile()) {
|
||||
continue;
|
||||
}
|
||||
const relativeTarget = path.relative(targetBase, entryPath).split(path.sep).join("/");
|
||||
if (targetSuffix && !relativeTarget.endsWith(targetSuffix)) {
|
||||
continue;
|
||||
}
|
||||
const wildcardValue = targetSuffix
|
||||
? relativeTarget.slice(0, -targetSuffix.length)
|
||||
: relativeTarget;
|
||||
targets.set(`${exportPrefix}${wildcardValue}${exportSuffix}`, entryPath);
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
function collectRuntimePackageImportTargets(
|
||||
dependencyRoot: string,
|
||||
pkg: RuntimeDependencyPackageJson,
|
||||
): Map<string, string> {
|
||||
const targets = new Map<string, string>();
|
||||
const exportsField = pkg.exports;
|
||||
if (
|
||||
exportsField &&
|
||||
typeof exportsField === "object" &&
|
||||
!Array.isArray(exportsField) &&
|
||||
Object.keys(exportsField).some((key) => key.startsWith("."))
|
||||
) {
|
||||
for (const [exportKey, exportValue] of Object.entries(exportsField)) {
|
||||
if (!exportKey.startsWith(".")) {
|
||||
continue;
|
||||
}
|
||||
const resolved = resolveRuntimePackageImportTarget(exportValue);
|
||||
if (resolved) {
|
||||
if (exportKey.includes("*")) {
|
||||
for (const [wildcardExportKey, targetPath] of collectRuntimePackageWildcardImportTargets(
|
||||
dependencyRoot,
|
||||
exportKey,
|
||||
resolved,
|
||||
)) {
|
||||
targets.set(wildcardExportKey, targetPath);
|
||||
}
|
||||
} else {
|
||||
targets.set(exportKey, resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
const rootEntry = resolveRuntimePackageImportTarget(exportsField) ?? pkg.module ?? pkg.main;
|
||||
if (rootEntry) {
|
||||
targets.set(".", rootEntry);
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
export function clearBundledRuntimeDependencyJitiAliases(): void {
|
||||
bundledRuntimeDependencyJitiAliases.clear();
|
||||
}
|
||||
|
||||
export function registerBundledRuntimeDependencyJitiAliases(rootDir: string): void {
|
||||
const rootPackageJson = readRuntimeDependencyPackageJson(path.join(rootDir, "package.json"));
|
||||
if (!rootPackageJson) {
|
||||
return;
|
||||
}
|
||||
for (const dependencyName of collectRuntimeDependencyNames(rootPackageJson)) {
|
||||
const dependencyPackageJsonPath = path.join(
|
||||
rootDir,
|
||||
"node_modules",
|
||||
...dependencyName.split("/"),
|
||||
"package.json",
|
||||
);
|
||||
const dependencyPackageJson = readRuntimeDependencyPackageJson(dependencyPackageJsonPath);
|
||||
if (!dependencyPackageJson) {
|
||||
continue;
|
||||
}
|
||||
const dependencyRoot = path.dirname(dependencyPackageJsonPath);
|
||||
for (const [exportKey, entry] of collectRuntimePackageImportTargets(
|
||||
dependencyRoot,
|
||||
dependencyPackageJson,
|
||||
)) {
|
||||
if (!entry || entry.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
const targetPath = path.resolve(dependencyRoot, entry);
|
||||
if (!isPathInside(dependencyRoot, targetPath) || !fs.existsSync(targetPath)) {
|
||||
continue;
|
||||
}
|
||||
const aliasKey =
|
||||
exportKey === "." ? dependencyName : `${dependencyName}${exportKey.slice(1)}`;
|
||||
bundledRuntimeDependencyJitiAliases.set(aliasKey, normalizeJitiAliasTargetPath(targetPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveBundledRuntimeDependencyJitiAliasMap(): Record<string, string> | undefined {
|
||||
if (bundledRuntimeDependencyJitiAliases.size === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return Object.fromEntries(
|
||||
[...bundledRuntimeDependencyJitiAliases.entries()].toSorted(
|
||||
([left], [right]) => right.length - left.length || left.localeCompare(right),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -31,6 +31,11 @@ import { resolveUserPath } from "../utils.js";
|
||||
import { resolvePluginActivationSourceConfig } from "./activation-source-config.js";
|
||||
import { buildPluginApi } from "./api-builder.js";
|
||||
import { inspectBundleMcpRuntimeSupport } from "./bundle-mcp.js";
|
||||
import {
|
||||
clearBundledRuntimeDependencyJitiAliases,
|
||||
registerBundledRuntimeDependencyJitiAliases,
|
||||
resolveBundledRuntimeDependencyJitiAliasMap,
|
||||
} from "./bundled-runtime-deps-jiti-aliases.js";
|
||||
import {
|
||||
clearBundledRuntimeDependencyNodePaths,
|
||||
installBundledRuntimeDeps,
|
||||
@@ -130,7 +135,6 @@ import {
|
||||
resolvePluginSdkAliasFile,
|
||||
resolvePluginRuntimeModulePath,
|
||||
resolvePluginSdkScopedAliasMap,
|
||||
normalizeJitiAliasTargetPath,
|
||||
shouldPreferNativeJiti,
|
||||
} from "./sdk-alias.js";
|
||||
import { hasKind, kindsEqual } from "./slots.js";
|
||||
@@ -273,7 +277,7 @@ export function clearPluginLoaderCache(): void {
|
||||
pluginLoaderCacheState.clear();
|
||||
clearBundledRuntimeDependencyNodePaths();
|
||||
clearBundledRuntimeDistMirrorPreparationCache();
|
||||
bundledRuntimeDependencyJitiAliases.clear();
|
||||
clearBundledRuntimeDependencyJitiAliases();
|
||||
clearAgentHarnesses();
|
||||
clearPluginCommands();
|
||||
clearCompactionProviders();
|
||||
@@ -450,206 +454,6 @@ function runPluginRegisterSync(
|
||||
}
|
||||
}
|
||||
|
||||
type RuntimeDependencyPackageJson = {
|
||||
dependencies?: Record<string, unknown>;
|
||||
optionalDependencies?: Record<string, unknown>;
|
||||
peerDependencies?: Record<string, unknown>;
|
||||
exports?: unknown;
|
||||
module?: string;
|
||||
main?: string;
|
||||
};
|
||||
|
||||
const bundledRuntimeDependencyJitiAliases = new Map<string, string>();
|
||||
|
||||
function readRuntimeDependencyPackageJson(
|
||||
packageJsonPath: string,
|
||||
): RuntimeDependencyPackageJson | null {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) as RuntimeDependencyPackageJson;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function collectRuntimeDependencyNames(pkg: RuntimeDependencyPackageJson): string[] {
|
||||
return [
|
||||
...Object.keys(pkg.dependencies ?? {}),
|
||||
...Object.keys(pkg.optionalDependencies ?? {}),
|
||||
...Object.keys(pkg.peerDependencies ?? {}),
|
||||
].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function resolveRuntimePackageImportTarget(exportsField: unknown): string | null {
|
||||
if (typeof exportsField === "string") {
|
||||
return exportsField;
|
||||
}
|
||||
if (Array.isArray(exportsField)) {
|
||||
for (const entry of exportsField) {
|
||||
const resolved = resolveRuntimePackageImportTarget(entry);
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (!exportsField || typeof exportsField !== "object" || Array.isArray(exportsField)) {
|
||||
return null;
|
||||
}
|
||||
const record = exportsField as Record<string, unknown>;
|
||||
if (Object.prototype.hasOwnProperty.call(record, ".")) {
|
||||
return resolveRuntimePackageImportTarget(record["."]);
|
||||
}
|
||||
for (const condition of ["import", "node", "default"] as const) {
|
||||
const resolved = resolveRuntimePackageImportTarget(record[condition]);
|
||||
if (resolved) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectRuntimePackageWildcardImportTargets(
|
||||
dependencyRoot: string,
|
||||
exportKey: string,
|
||||
targetPattern: string,
|
||||
): Map<string, string> {
|
||||
const targets = new Map<string, string>();
|
||||
const wildcardIndex = exportKey.indexOf("*");
|
||||
const targetWildcardIndex = targetPattern.indexOf("*");
|
||||
if (wildcardIndex === -1 || targetWildcardIndex === -1) {
|
||||
return targets;
|
||||
}
|
||||
const exportPrefix = exportKey.slice(0, wildcardIndex);
|
||||
const exportSuffix = exportKey.slice(wildcardIndex + 1);
|
||||
const targetPrefix = targetPattern.slice(0, targetWildcardIndex);
|
||||
const targetSuffix = targetPattern.slice(targetWildcardIndex + 1);
|
||||
const targetBase = path.resolve(dependencyRoot, targetPrefix);
|
||||
if (!isPathInside(dependencyRoot, targetBase) || !safeStatSync(targetBase)?.isDirectory()) {
|
||||
return targets;
|
||||
}
|
||||
const stack = [targetBase];
|
||||
while (stack.length > 0) {
|
||||
const currentDir = stack.pop();
|
||||
if (!currentDir) {
|
||||
continue;
|
||||
}
|
||||
let entries: fs.Dirent[];
|
||||
try {
|
||||
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(currentDir, entry.name);
|
||||
if (!isPathInside(dependencyRoot, entryPath)) {
|
||||
continue;
|
||||
}
|
||||
if (entry.isDirectory()) {
|
||||
stack.push(entryPath);
|
||||
continue;
|
||||
}
|
||||
if (!entry.isFile()) {
|
||||
continue;
|
||||
}
|
||||
const relativeTarget = path.relative(targetBase, entryPath).split(path.sep).join("/");
|
||||
if (targetSuffix && !relativeTarget.endsWith(targetSuffix)) {
|
||||
continue;
|
||||
}
|
||||
const wildcardValue = targetSuffix
|
||||
? relativeTarget.slice(0, -targetSuffix.length)
|
||||
: relativeTarget;
|
||||
targets.set(`${exportPrefix}${wildcardValue}${exportSuffix}`, entryPath);
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
function collectRuntimePackageImportTargets(
|
||||
dependencyRoot: string,
|
||||
pkg: RuntimeDependencyPackageJson,
|
||||
): Map<string, string> {
|
||||
const targets = new Map<string, string>();
|
||||
const exportsField = pkg.exports;
|
||||
if (
|
||||
exportsField &&
|
||||
typeof exportsField === "object" &&
|
||||
!Array.isArray(exportsField) &&
|
||||
Object.keys(exportsField).some((key) => key.startsWith("."))
|
||||
) {
|
||||
for (const [exportKey, exportValue] of Object.entries(exportsField)) {
|
||||
if (!exportKey.startsWith(".")) {
|
||||
continue;
|
||||
}
|
||||
const resolved = resolveRuntimePackageImportTarget(exportValue);
|
||||
if (resolved) {
|
||||
if (exportKey.includes("*")) {
|
||||
for (const [wildcardExportKey, targetPath] of collectRuntimePackageWildcardImportTargets(
|
||||
dependencyRoot,
|
||||
exportKey,
|
||||
resolved,
|
||||
)) {
|
||||
targets.set(wildcardExportKey, targetPath);
|
||||
}
|
||||
} else {
|
||||
targets.set(exportKey, resolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
const rootEntry = resolveRuntimePackageImportTarget(exportsField) ?? pkg.module ?? pkg.main;
|
||||
if (rootEntry) {
|
||||
targets.set(".", rootEntry);
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
function registerBundledRuntimeDependencyJitiAliases(rootDir: string): void {
|
||||
const rootPackageJson = readRuntimeDependencyPackageJson(path.join(rootDir, "package.json"));
|
||||
if (!rootPackageJson) {
|
||||
return;
|
||||
}
|
||||
for (const dependencyName of collectRuntimeDependencyNames(rootPackageJson)) {
|
||||
const dependencyPackageJsonPath = path.join(
|
||||
rootDir,
|
||||
"node_modules",
|
||||
...dependencyName.split("/"),
|
||||
"package.json",
|
||||
);
|
||||
const dependencyPackageJson = readRuntimeDependencyPackageJson(dependencyPackageJsonPath);
|
||||
if (!dependencyPackageJson) {
|
||||
continue;
|
||||
}
|
||||
const dependencyRoot = path.dirname(dependencyPackageJsonPath);
|
||||
for (const [exportKey, entry] of collectRuntimePackageImportTargets(
|
||||
dependencyRoot,
|
||||
dependencyPackageJson,
|
||||
)) {
|
||||
if (!entry || entry.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
const targetPath = path.resolve(dependencyRoot, entry);
|
||||
if (!isPathInside(dependencyRoot, targetPath) || !fs.existsSync(targetPath)) {
|
||||
continue;
|
||||
}
|
||||
const aliasKey =
|
||||
exportKey === "." ? dependencyName : `${dependencyName}${exportKey.slice(1)}`;
|
||||
bundledRuntimeDependencyJitiAliases.set(aliasKey, normalizeJitiAliasTargetPath(targetPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolveBundledRuntimeDependencyJitiAliasMap(): Record<string, string> | undefined {
|
||||
if (bundledRuntimeDependencyJitiAliases.size === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return Object.fromEntries(
|
||||
[...bundledRuntimeDependencyJitiAliases.entries()].toSorted(
|
||||
([left], [right]) => right.length - left.length || left.localeCompare(right),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function createPluginJitiLoader(options: Pick<PluginLoadOptions, "pluginSdkResolution">) {
|
||||
const jitiLoaders: PluginJitiLoaderCache = new Map();
|
||||
return (modulePath: string) => {
|
||||
|
||||
Reference in New Issue
Block a user