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(),
|
loadClawdbotPlugins: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../plugins/manifest-registry.js", () => ({
|
||||||
|
loadPluginManifestRegistry: vi.fn(() => ({ plugins: [], diagnostics: [] })),
|
||||||
|
}));
|
||||||
|
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import type { ChannelPluginCatalogEntry } from "../../channels/plugins/catalog.js";
|
import type { ChannelPluginCatalogEntry } from "../../channels/plugins/catalog.js";
|
||||||
import type { ClawdbotConfig } from "../../config/config.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 { enablePluginInConfig } from "../../plugins/enable.js";
|
||||||
import { loadClawdbotPlugins } from "../../plugins/loader.js";
|
import { loadClawdbotPlugins } from "../../plugins/loader.js";
|
||||||
import { installPluginFromNpmSpec } from "../../plugins/install.js";
|
import { installPluginFromNpmSpec } from "../../plugins/install.js";
|
||||||
|
import { loadPluginManifestRegistry } from "../../plugins/manifest-registry.js";
|
||||||
import type { RuntimeEnv } from "../../runtime.js";
|
import type { RuntimeEnv } from "../../runtime.js";
|
||||||
import type { WizardPrompter } from "../../wizard/prompts.js";
|
import type { WizardPrompter } from "../../wizard/prompts.js";
|
||||||
|
|
||||||
@ -18,6 +19,24 @@ type InstallResult = {
|
|||||||
installed: boolean;
|
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 {
|
function hasGitWorkspace(workspaceDir?: string): boolean {
|
||||||
const candidates = new Set<string>();
|
const candidates = new Set<string>();
|
||||||
candidates.add(path.join(process.cwd(), ".git"));
|
candidates.add(path.join(process.cwd(), ".git"));
|
||||||
@ -122,7 +141,24 @@ export async function ensureOnboardingPluginInstalled(params: {
|
|||||||
}): Promise<InstallResult> {
|
}): Promise<InstallResult> {
|
||||||
const { entry, prompter, runtime, workspaceDir } = params;
|
const { entry, prompter, runtime, workspaceDir } = params;
|
||||||
let next = params.cfg;
|
let next = params.cfg;
|
||||||
|
|
||||||
const allowLocal = hasGitWorkspace(workspaceDir);
|
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 localPath = resolveLocalPath(entry, workspaceDir, allowLocal);
|
||||||
const defaultChoice = resolveInstallDefaultChoice({
|
const defaultChoice = resolveInstallDefaultChoice({
|
||||||
cfg: next,
|
cfg: next,
|
||||||
|
|||||||
@ -162,8 +162,23 @@ async function installPluginFromPackageDir(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deps = manifest.dependencies ?? {};
|
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) {
|
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…");
|
logger.info?.("Installing plugin dependencies…");
|
||||||
const npmRes = await runCommandWithTimeout(["npm", "install", "--omit=dev", "--silent"], {
|
const npmRes = await runCommandWithTimeout(["npm", "install", "--omit=dev", "--silent"], {
|
||||||
timeoutMs: Math.max(timeoutMs, 300_000),
|
timeoutMs: Math.max(timeoutMs, 300_000),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user