refactor(plugins): reuse dependency status core

This commit is contained in:
Vincent Koc
2026-06-16 13:56:59 +08:00
parent 0fd0e7cb92
commit 2e7caba557
2 changed files with 2 additions and 101 deletions

View File

@@ -1,99 +0,0 @@
/** Builds dependency install status for plugin status snapshots without importing discovery. */
import fs from "node:fs";
import path from "node:path";
import type {
PluginDependencyEntry,
PluginDependencySpecMap,
PluginDependencyStatus,
} from "./status-dependencies.js";
// Keep this helper separate from status-dependencies.ts. That module is also
// imported by plugin discovery, and the CLI startup bundle otherwise pulls the
// full discovery chunk for `plugins list --json`.
function dependencyPathSegments(name: string): string[] | null {
const segments = name.split("/");
if (segments.length === 1 && segments[0]) {
return [segments[0]];
}
if (segments.length === 2 && segments[0]?.startsWith("@") && segments[1]) {
return segments;
}
return null;
}
function findDependencyPackageDir(params: { fromDir: string; name: string }): string | undefined {
const segments = dependencyPathSegments(params.name);
if (!segments) {
return undefined;
}
let current = path.resolve(params.fromDir);
while (true) {
const candidate = path.join(current, "node_modules", ...segments);
if (fs.existsSync(candidate)) {
return candidate;
}
const parent = path.dirname(current);
if (parent === current) {
return undefined;
}
current = parent;
}
}
function buildDependencyEntries(params: {
rootDir: string | undefined;
dependencies: PluginDependencySpecMap;
optional: boolean;
}): PluginDependencyEntry[] {
return Object.entries(params.dependencies)
.toSorted(([left], [right]) => left.localeCompare(right))
.map(([name, spec]) => {
const resolvedPath = params.rootDir
? findDependencyPackageDir({ fromDir: params.rootDir, name })
: undefined;
const entry: PluginDependencyEntry = {
name,
spec,
installed: resolvedPath !== undefined,
optional: params.optional,
};
if (resolvedPath) {
entry.resolvedPath = resolvedPath;
}
return entry;
});
}
/** Resolves required and optional dependency status for one plugin root. */
export function buildSnapshotPluginDependencyStatus(params: {
rootDir?: string;
dependencies?: PluginDependencySpecMap;
optionalDependencies?: PluginDependencySpecMap;
}): PluginDependencyStatus {
const dependencies = buildDependencyEntries({
rootDir: params.rootDir,
dependencies: params.dependencies ?? {},
optional: false,
});
const optionalDependencies = buildDependencyEntries({
rootDir: params.rootDir,
dependencies: params.optionalDependencies ?? {},
optional: true,
});
const missing = dependencies.filter((entry) => !entry.installed).map((entry) => entry.name);
const missingOptional = optionalDependencies
.filter((entry) => !entry.installed)
.map((entry) => entry.name);
const requiredInstalled = missing.length === 0;
const optionalInstalled = missingOptional.length === 0;
return {
hasDependencies: dependencies.length > 0 || optionalDependencies.length > 0,
installed: requiredInstalled,
requiredInstalled,
optionalInstalled,
missing,
missingOptional,
dependencies,
optionalDependencies,
};
}

View File

@@ -8,7 +8,7 @@ import {
type PluginRegistrySnapshotSource,
} from "./plugin-registry.js";
import { createEmptyPluginRegistry, type PluginRecord, type PluginRegistry } from "./registry.js";
import { buildSnapshotPluginDependencyStatus } from "./status-snapshot-dependencies.js";
import { buildPluginDependencyStatus } from "./status-dependencies-core.js";
import type { PluginLogger } from "./types.js";
/** Control-plane plugin status shape used by `openclaw plugins status` style surfaces. */
@@ -118,7 +118,7 @@ function buildPluginRecordFromInstalledIndex(
hookCount: 0,
configSchema: false,
contracts: manifest?.contracts,
dependencyStatus: buildSnapshotPluginDependencyStatus({
dependencyStatus: buildPluginDependencyStatus({
rootDir: plugin.rootDir,
dependencies: manifest?.packageDependencies,
optionalDependencies: manifest?.packageOptionalDependencies,