feat(plugins): support npm pack installs

This commit is contained in:
Peter Steinberger
2026-05-06 09:16:39 +01:00
parent 54e23b6d11
commit 2eaf8ad712
11 changed files with 711 additions and 147 deletions

View File

@@ -16,6 +16,7 @@ import {
findBundledPluginSourceMock,
installHooksFromNpmSpec,
installHooksFromPath,
installPluginFromNpmPackArchive,
installPluginFromClawHub,
installPluginFromGitSpec,
installPluginFromMarketplace,
@@ -127,6 +128,28 @@ function createNpmPluginInstallResult(
};
}
function createNpmPackPluginInstallResult(
pluginId = "demo",
): Awaited<ReturnType<typeof installPluginFromNpmPackArchive>> {
return {
ok: true,
pluginId,
targetDir: cliInstallPath(pluginId),
version: "1.2.3",
extensions: ["dist/index.js"],
manifestName: `@openclaw/${pluginId}`,
npmTarballName: `openclaw-${pluginId}-1.2.3.tgz`,
npmResolution: {
name: `@openclaw/${pluginId}`,
version: "1.2.3",
resolvedSpec: `@openclaw/${pluginId}@1.2.3`,
integrity: "sha512-pack-demo",
shasum: "packdemosha",
resolvedAt: "2026-05-06T00:00:00.000Z",
},
};
}
function createGitPluginInstallResult(
pluginId = "demo",
): Awaited<ReturnType<typeof installPluginFromGitSpec>> {
@@ -909,6 +932,47 @@ describe("plugins cli install", () => {
expect(writeConfigFile).toHaveBeenCalledWith(enabledCfg);
});
it("installs npm-pack archives through npm install semantics", async () => {
const cfg = createEmptyPluginConfig();
const enabledCfg = createEnabledPluginConfig("demo");
const archivePath = "/tmp/openclaw-demo-1.2.3.tgz";
loadConfig.mockReturnValue(cfg);
installPluginFromNpmPackArchive.mockResolvedValue(createNpmPackPluginInstallResult("demo"));
enablePluginInConfig.mockReturnValue({ config: enabledCfg });
recordPluginInstall.mockReturnValue(enabledCfg);
applyExclusiveSlotSelection.mockReturnValue({
config: enabledCfg,
warnings: [],
});
await runPluginsCommand(["plugins", "install", `npm-pack:${archivePath}`]);
expect(installPluginFromNpmPackArchive).toHaveBeenCalledWith(
expect.objectContaining({
archivePath,
mode: "install",
}),
);
expect(installPluginFromPath).not.toHaveBeenCalled();
expect(installPluginFromNpmSpec).not.toHaveBeenCalled();
expect(writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith({
demo: expect.objectContaining({
source: "npm",
spec: "@openclaw/demo@1.2.3",
sourcePath: archivePath,
installPath: cliInstallPath("demo"),
version: "1.2.3",
artifactKind: "npm-pack",
artifactFormat: "tgz",
npmIntegrity: "sha512-pack-demo",
npmShasum: "packdemosha",
npmTarballName: "openclaw-demo-1.2.3.tgz",
}),
});
expect(writeConfigFile).toHaveBeenCalledWith(enabledCfg);
});
it("keeps npm-prefixed official plugin ids on explicit npm semantics", async () => {
const cfg = createEmptyPluginConfig();
const enabledCfg = createEnabledPluginConfig("brave");