mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:10:43 +00:00
fix(plugins): keep managed npm mutations in legacy peer mode
This commit is contained in:
@@ -62,6 +62,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/update: move ClawHub-preferred externalized plugin installs back to ClawHub after an earlier npm fallback once the ClawHub package becomes available. Thanks @vincentkoc.
|
||||
- Plugins/update: clean stale bundled load paths for already-externalized pinned npm and ClawHub plugin installs, so release-channel sync does not leave removed bundled paths ahead of the installed external package. Thanks @vincentkoc.
|
||||
- Plugins/update: repair stale managed npm-root `openclaw` peer packages before plugin installs, so beta-channel official plugin updates are not downgraded by old core package-lock state. Thanks @vincentkoc.
|
||||
- Plugins/install: run managed npm-root install, rollback, repair, and uninstall mutations with legacy peer resolution so removing one plugin cannot rehydrate a stale registry `openclaw` package into the shared root. Thanks @vincentkoc.
|
||||
- Plugins/install: reassert managed npm plugin `openclaw` peer links after shared-root npm installs, updates, and uninstalls, so mutating one plugin does not leave previously installed SDK-using plugins unable to resolve `openclaw/plugin-sdk/*`.
|
||||
- Plugins/update: make package upgrades swap pnpm/npm-prefix installs cleanly, keep legacy plugin install runtime chunks working, and on the beta channel fall back default-line npm plugins to default/latest when plugin beta releases are missing or fail install validation. Thanks @vincentkoc and @joshavant.
|
||||
- Plugins/active-memory: skip session-store channel entries that contain `:` when resolving the recall subagent's channel, so QQ c2c agent IDs (e.g. `c2c:10D4F7C2…`) and other scoped conversation IDs do not reach bundled-plugin `dirName` validation and crash the recall run. The same guard already applied to explicit `channelId` params (#76704); this extends it to store-derived channels. (#77396) Thanks @hclsys.
|
||||
|
||||
@@ -271,6 +271,7 @@ describe("managed npm root", () => {
|
||||
"npm",
|
||||
"uninstall",
|
||||
"--loglevel=error",
|
||||
"--legacy-peer-deps",
|
||||
"--ignore-scripts",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
@@ -280,6 +281,9 @@ describe("managed npm root", () => {
|
||||
],
|
||||
expect.objectContaining({
|
||||
cwd: npmRoot,
|
||||
env: expect.objectContaining({
|
||||
npm_config_legacy_peer_deps: "true",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ export async function repairManagedNpmRootOpenClawPeer(params: {
|
||||
"npm",
|
||||
"uninstall",
|
||||
"--loglevel=error",
|
||||
"--legacy-peer-deps",
|
||||
"--ignore-scripts",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
@@ -118,6 +119,7 @@ export async function repairManagedNpmRootOpenClawPeer(params: {
|
||||
"npm",
|
||||
"prune",
|
||||
"--loglevel=error",
|
||||
"--legacy-peer-deps",
|
||||
"--ignore-scripts",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
@@ -128,7 +130,11 @@ export async function repairManagedNpmRootOpenClawPeer(params: {
|
||||
const result = await command(npmArgs, {
|
||||
cwd: params.npmRoot,
|
||||
timeoutMs: Math.max(params.timeoutMs ?? 300_000, 300_000),
|
||||
env: createSafeNpmInstallEnv(process.env, { packageLock: true, quiet: true }),
|
||||
env: createSafeNpmInstallEnv(process.env, {
|
||||
legacyPeerDeps: true,
|
||||
packageLock: true,
|
||||
quiet: true,
|
||||
}),
|
||||
});
|
||||
if (result.code !== 0) {
|
||||
params.logger?.warn?.(
|
||||
|
||||
@@ -7,6 +7,7 @@ describe("safe npm install helpers", () => {
|
||||
createSafeNpmInstallArgs({
|
||||
omitDev: true,
|
||||
ignoreWorkspaces: true,
|
||||
legacyPeerDeps: true,
|
||||
loglevel: "error",
|
||||
noAudit: true,
|
||||
noFund: true,
|
||||
@@ -15,6 +16,7 @@ describe("safe npm install helpers", () => {
|
||||
"install",
|
||||
"--omit=dev",
|
||||
"--loglevel=error",
|
||||
"--legacy-peer-deps",
|
||||
"--ignore-scripts",
|
||||
"--workspaces=false",
|
||||
"--no-audit",
|
||||
|
||||
@@ -10,6 +10,7 @@ type SafeNpmInstallEnvOptions = NpmProjectInstallEnvOptions & {
|
||||
|
||||
type SafeNpmInstallArgsOptions = {
|
||||
ignoreWorkspaces?: boolean;
|
||||
legacyPeerDeps?: boolean;
|
||||
loglevel?: "error" | "silent";
|
||||
noAudit?: boolean;
|
||||
noFund?: boolean;
|
||||
@@ -48,6 +49,7 @@ export function createSafeNpmInstallArgs(options: SafeNpmInstallArgsOptions = {}
|
||||
"install",
|
||||
...(options.omitDev ? ["--omit=dev"] : []),
|
||||
...(options.loglevel ? [`--loglevel=${options.loglevel}`] : []),
|
||||
...(options.legacyPeerDeps ? ["--legacy-peer-deps"] : []),
|
||||
"--ignore-scripts",
|
||||
...(options.ignoreWorkspaces ? ["--workspaces=false"] : []),
|
||||
...(options.noAudit ? ["--no-audit"] : []),
|
||||
|
||||
@@ -64,6 +64,7 @@ function expectNpmInstallIntoRoot(params: { calls: unknown[][]; npmRoot: string
|
||||
"install",
|
||||
"--omit=dev",
|
||||
"--loglevel=error",
|
||||
"--legacy-peer-deps",
|
||||
"--ignore-scripts",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
@@ -922,6 +923,9 @@ describe("installPluginFromNpmSpec", () => {
|
||||
};
|
||||
}
|
||||
if (argv[0] === "npm" && argv[1] === "uninstall") {
|
||||
if (!argv.includes("--legacy-peer-deps")) {
|
||||
fs.mkdirSync(path.join(npmRoot, "node_modules", "openclaw"), { recursive: true });
|
||||
}
|
||||
return successfulSpawn("");
|
||||
}
|
||||
throw new Error(`unexpected command: ${argv.join(" ")}`);
|
||||
@@ -945,6 +949,9 @@ describe("installPluginFromNpmSpec", () => {
|
||||
dependencies: {},
|
||||
});
|
||||
expect(fs.lstatSync(peerLink).isSymbolicLink()).toBe(true);
|
||||
await expect(
|
||||
fs.promises.access(path.join(npmRoot, "node_modules", "openclaw")),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("rolls back installed npm package debris when security scan blocks the plugin", async () => {
|
||||
|
||||
@@ -323,6 +323,7 @@ async function rollbackManagedNpmPluginInstall(params: {
|
||||
"npm",
|
||||
"uninstall",
|
||||
"--loglevel=error",
|
||||
"--legacy-peer-deps",
|
||||
"--ignore-scripts",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
@@ -333,7 +334,11 @@ async function rollbackManagedNpmPluginInstall(params: {
|
||||
{
|
||||
cwd: params.npmRoot,
|
||||
timeoutMs: Math.max(params.timeoutMs, 300_000),
|
||||
env: createSafeNpmInstallEnv(process.env, { packageLock: true, quiet: true }),
|
||||
env: createSafeNpmInstallEnv(process.env, {
|
||||
legacyPeerDeps: true,
|
||||
packageLock: true,
|
||||
quiet: true,
|
||||
}),
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -487,6 +492,7 @@ async function installPluginFromManagedNpmRoot(
|
||||
...createSafeNpmInstallArgs({
|
||||
omitDev: true,
|
||||
loglevel: "error",
|
||||
legacyPeerDeps: true,
|
||||
noAudit: true,
|
||||
noFund: true,
|
||||
}),
|
||||
@@ -496,7 +502,11 @@ async function installPluginFromManagedNpmRoot(
|
||||
{
|
||||
cwd: npmRoot,
|
||||
timeoutMs: Math.max(timeoutMs, 300_000),
|
||||
env: createSafeNpmInstallEnv(process.env, { packageLock: true, quiet: true }),
|
||||
env: createSafeNpmInstallEnv(process.env, {
|
||||
legacyPeerDeps: true,
|
||||
packageLock: true,
|
||||
quiet: true,
|
||||
}),
|
||||
},
|
||||
);
|
||||
if (install.code !== 0) {
|
||||
|
||||
@@ -974,6 +974,7 @@ describe("uninstallPlugin", () => {
|
||||
"npm",
|
||||
"uninstall",
|
||||
"--loglevel=error",
|
||||
"--legacy-peer-deps",
|
||||
"--ignore-scripts",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
@@ -986,6 +987,7 @@ describe("uninstallPlugin", () => {
|
||||
timeoutMs: 300_000,
|
||||
env: expect.objectContaining({
|
||||
NPM_CONFIG_IGNORE_SCRIPTS: "true",
|
||||
npm_config_legacy_peer_deps: "true",
|
||||
npm_config_package_lock: "true",
|
||||
}),
|
||||
}),
|
||||
@@ -1015,8 +1017,11 @@ describe("uninstallPlugin", () => {
|
||||
)}\n`,
|
||||
);
|
||||
await fs.symlink(tempDir, peerLink, "junction");
|
||||
runCommandWithTimeoutMock.mockImplementationOnce(async () => {
|
||||
runCommandWithTimeoutMock.mockImplementationOnce(async (argv: string[]) => {
|
||||
await fs.rm(peerLink, { recursive: true, force: true });
|
||||
if (!argv.includes("--legacy-peer-deps")) {
|
||||
await fs.mkdir(path.join(npmRoot, "node_modules", "openclaw"), { recursive: true });
|
||||
}
|
||||
return {
|
||||
code: 0,
|
||||
stdout: "",
|
||||
@@ -1038,6 +1043,7 @@ describe("uninstallPlugin", () => {
|
||||
|
||||
expect(applied).toEqual({ directoryRemoved: true, warnings: [] });
|
||||
await expect(fs.access(removedPluginDir)).rejects.toThrow();
|
||||
await expect(fs.access(path.join(npmRoot, "node_modules", "openclaw"))).rejects.toThrow();
|
||||
await expect(fs.lstat(peerLink).then((stat) => stat.isSymbolicLink())).resolves.toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -595,6 +595,7 @@ export async function applyPluginUninstallDirectoryRemoval(
|
||||
"npm",
|
||||
"uninstall",
|
||||
"--loglevel=error",
|
||||
"--legacy-peer-deps",
|
||||
"--ignore-scripts",
|
||||
"--no-audit",
|
||||
"--no-fund",
|
||||
@@ -605,7 +606,11 @@ export async function applyPluginUninstallDirectoryRemoval(
|
||||
{
|
||||
cwd: removal.cleanup.npmRoot,
|
||||
timeoutMs: 300_000,
|
||||
env: createSafeNpmInstallEnv(process.env, { packageLock: true, quiet: true }),
|
||||
env: createSafeNpmInstallEnv(process.env, {
|
||||
legacyPeerDeps: true,
|
||||
packageLock: true,
|
||||
quiet: true,
|
||||
}),
|
||||
},
|
||||
);
|
||||
if (uninstall.code !== 0) {
|
||||
|
||||
Reference in New Issue
Block a user