fix(plugin-install): handle existing plugins and filter workspace deps
Skip installation if plugin already exists in registry, unless it's a bundled plugin with local workspace. Filter out workspace:* dependencies before npm install to prevent installation errors in standalone packages.
This commit is contained in:
parent
dce7925e2a
commit
47fdadef6a
@ -16,6 +16,10 @@ vi.mock("../../plugins/loader.js", () => ({
|
||||
loadClawdbotPlugins: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../plugins/manifest-registry.js", () => ({
|
||||
loadPluginManifestRegistry: vi.fn(() => ({ plugins: [], diagnostics: [] })),
|
||||
}));
|
||||
|
||||
import fs from "node:fs";
|
||||
import type { ChannelPluginCatalogEntry } from "../../channels/plugins/catalog.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
|
||||
@ -8,6 +8,7 @@ import { recordPluginInstall } from "../../plugins/installs.js";
|
||||
import { enablePluginInConfig } from "../../plugins/enable.js";
|
||||
import { loadClawdbotPlugins } from "../../plugins/loader.js";
|
||||
import { installPluginFromNpmSpec } from "../../plugins/install.js";
|
||||
import { loadPluginManifestRegistry } from "../../plugins/manifest-registry.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import type { WizardPrompter } from "../../wizard/prompts.js";
|
||||
|
||||
@ -18,6 +19,24 @@ type InstallResult = {
|
||||
installed: boolean;
|
||||
};
|
||||
|
||||
function findExistingPluginOrigin(params: {
|
||||
pluginId: string;
|
||||
cfg: ClawdbotConfig;
|
||||
workspaceDir?: string;
|
||||
}): "config" | "workspace" | "global" | "bundled" | null {
|
||||
const workspaceDir =
|
||||
params.workspaceDir ??
|
||||
resolveAgentWorkspaceDir(params.cfg, resolveDefaultAgentId(params.cfg)) ??
|
||||
undefined;
|
||||
const registry = loadPluginManifestRegistry({
|
||||
config: params.cfg,
|
||||
workspaceDir,
|
||||
cache: false,
|
||||
});
|
||||
const found = registry.plugins.find((plugin) => plugin.id === params.pluginId);
|
||||
return found?.origin ?? null;
|
||||
}
|
||||
|
||||
function hasGitWorkspace(workspaceDir?: string): boolean {
|
||||
const candidates = new Set<string>();
|
||||
candidates.add(path.join(process.cwd(), ".git"));
|
||||
@ -122,7 +141,24 @@ export async function ensureOnboardingPluginInstalled(params: {
|
||||
}): Promise<InstallResult> {
|
||||
const { entry, prompter, runtime, workspaceDir } = params;
|
||||
let next = params.cfg;
|
||||
|
||||
const allowLocal = hasGitWorkspace(workspaceDir);
|
||||
const existingOrigin = findExistingPluginOrigin({
|
||||
pluginId: entry.id,
|
||||
cfg: next,
|
||||
workspaceDir,
|
||||
});
|
||||
if (existingOrigin && (existingOrigin !== "bundled" || !allowLocal)) {
|
||||
const enabled = enablePluginInConfig(next, entry.id);
|
||||
next = enabled.config;
|
||||
if (enabled.enabled) return { cfg: next, installed: true };
|
||||
await prompter.note(
|
||||
`Cannot enable ${entry.id}: ${enabled.reason ?? "plugin disabled"}.`,
|
||||
"Plugin install",
|
||||
);
|
||||
return { cfg: next, installed: false };
|
||||
}
|
||||
|
||||
const localPath = resolveLocalPath(entry, workspaceDir, allowLocal);
|
||||
const defaultChoice = resolveInstallDefaultChoice({
|
||||
cfg: next,
|
||||
|
||||
@ -162,8 +162,23 @@ async function installPluginFromPackageDir(params: {
|
||||
}
|
||||
|
||||
const deps = manifest.dependencies ?? {};
|
||||
const hasDeps = Object.keys(deps).length > 0;
|
||||
// Filter out workspace:* dependencies as they're not supported in standalone npm packages
|
||||
const filteredDeps = Object.fromEntries(
|
||||
Object.entries(deps).filter(([, version]) => !String(version).startsWith("workspace:")),
|
||||
);
|
||||
const hasDeps = Object.keys(filteredDeps).length > 0;
|
||||
if (hasDeps) {
|
||||
// Update package.json in targetDir to remove workspace: dependencies before npm install
|
||||
const targetManifestPath = path.join(targetDir, "package.json");
|
||||
const filteredManifest = {
|
||||
...manifest,
|
||||
dependencies: filteredDeps,
|
||||
};
|
||||
await fs.writeFile(
|
||||
targetManifestPath,
|
||||
JSON.stringify(filteredManifest, null, 2) + "\n",
|
||||
"utf-8",
|
||||
);
|
||||
logger.info?.("Installing plugin dependencies…");
|
||||
const npmRes = await runCommandWithTimeout(["npm", "install", "--omit=dev", "--silent"], {
|
||||
timeoutMs: Math.max(timeoutMs, 300_000),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user