fix(plugins): stop eager bundled plugin dep install (#69334)

* fix(plugins): stop eager bundled plugin dep install

* test(auto-reply): mock direct auth profile store imports
This commit is contained in:
Peter Steinberger
2026-04-20 14:41:18 +01:00
committed by GitHub
parent f006678f3c
commit 8a4332864b
3 changed files with 39 additions and 9 deletions

View File

@@ -1,10 +1,9 @@
#!/usr/bin/env node
// Runs after install to restore bundled extension runtime deps.
// Installed builds can lazy-load bundled plugin code through root dist chunks,
// so runtime dependencies declared in dist/extensions/*/package.json must also
// resolve from the package root node_modules. Source checkouts resolve bundled
// plugin deps from the workspace root, so stale plugin-local node_modules must
// not linger under extensions/* and shadow the root graph.
// Runs after install to keep packaged dist safe and compatible.
// Bundled extension runtime dependencies are extension-owned. Do not install
// every bundled extension dependency during core package install unless the
// legacy eager-install escape hatch is explicitly enabled; `openclaw doctor
// --fix` owns the repair path for extensions that are actually used.
import { spawnSync } from "node:child_process";
import { randomUUID } from "node:crypto";
import {
@@ -33,6 +32,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
const DEFAULT_EXTENSIONS_DIR = join(__dirname, "..", "dist", "extensions");
const DEFAULT_PACKAGE_ROOT = join(__dirname, "..");
const DISABLE_POSTINSTALL_ENV = "OPENCLAW_DISABLE_BUNDLED_PLUGIN_POSTINSTALL";
const EAGER_BUNDLED_PLUGIN_DEPS_ENV = "OPENCLAW_EAGER_BUNDLED_PLUGIN_DEPS";
const DIST_INVENTORY_PATH = "dist/postinstall-inventory.json";
const LEGACY_UPDATE_COMPAT_SIDECARS = [
{
@@ -465,6 +465,10 @@ export function createNestedNpmInstallEnv(env = process.env) {
return nextEnv;
}
function shouldEagerInstallBundledPluginDeps(env = process.env) {
return env?.[EAGER_BUNDLED_PLUGIN_DEPS_ENV]?.trim() === "1";
}
export function applyBaileysEncryptedStreamFinishHotfix(params = {}) {
const packageRoot = params.packageRoot ?? DEFAULT_PACKAGE_ROOT;
const pathExists = params.existsSync ?? existsSync;
@@ -714,6 +718,16 @@ export function runBundledPluginPostinstall(params = {}) {
) {
return;
}
if (!shouldEagerInstallBundledPluginDeps(env)) {
applyBundledPluginRuntimeHotfixes({
packageRoot,
existsSync: pathExists,
readFileSync: params.readFileSync,
writeFileSync: params.writeFileSync,
log,
});
return;
}
const runtimeDeps =
params.runtimeDeps ??
discoverBundledPluginRuntimeDeps({ extensionsDir, existsSync: pathExists });

View File

@@ -24,6 +24,18 @@ vi.mock("../../agents/auth-profiles.js", () => ({
resolveAuthStorePathForDisplay: () => "/tmp/auth-profiles.json",
}));
vi.mock("../../agents/auth-profiles/store.js", () => ({
ensureAuthProfileStore: () => ({
version: 1,
profiles: authProfilesStoreMock.profiles,
}),
findPersistedAuthProfileCredential: ({ profileId }: { profileId: string }) =>
authProfilesStoreMock.profiles[profileId],
hasAnyAuthProfileStoreSource: () => Object.keys(authProfilesStoreMock.profiles).length > 0,
saveAuthProfileStore: vi.fn(),
updateAuthProfileStoreWithLock: vi.fn(),
}));
import { resolveAgentDir, resolveSessionAgentId } from "../../agents/agent-scope.js";
import {
clearRuntimeAuthProfileStoreSnapshots,

View File

@@ -100,7 +100,7 @@ describe("bundled plugin postinstall", () => {
});
});
it("installs bundled plugin deps outside of source checkouts", async () => {
it("does not install bundled plugin deps outside of source checkouts by default", async () => {
const extensionsDir = await createExtensionsDir();
const packageRoot = path.dirname(path.dirname(extensionsDir));
await writePluginPackage(extensionsDir, "acpx", {
@@ -119,7 +119,7 @@ describe("bundled plugin postinstall", () => {
log: { log: vi.fn(), warn: vi.fn() },
});
expect(spawnSync).toHaveBeenCalled();
expect(spawnSync).not.toHaveBeenCalled();
});
it("prunes source-checkout bundled plugin node_modules", async () => {
@@ -431,6 +431,7 @@ describe("bundled plugin postinstall", () => {
runBundledPluginPostinstall({
env: {
OPENCLAW_EAGER_BUNDLED_PLUGIN_DEPS: "1",
npm_config_global: "true",
npm_config_location: "global",
npm_config_prefix: "/opt/homebrew",
@@ -494,7 +495,7 @@ describe("bundled plugin postinstall", () => {
const spawnSync = vi.fn(() => ({ status: 0, stderr: "", stdout: "" }));
runBundledPluginPostinstall({
env: { HOME: "/tmp/home" },
env: { HOME: "/tmp/home", OPENCLAW_EAGER_BUNDLED_PLUGIN_DEPS: "1" },
extensionsDir,
packageRoot,
arch: "arm64",
@@ -614,6 +615,7 @@ describe("bundled plugin postinstall", () => {
runBundledPluginPostinstall({
env: {
OPENCLAW_EAGER_BUNDLED_PLUGIN_DEPS: "1",
npm_config_global: "true",
npm_config_location: "global",
npm_config_prefix: "/opt/homebrew",
@@ -653,6 +655,7 @@ describe("bundled plugin postinstall", () => {
runBundledPluginPostinstall({
env: {
OPENCLAW_EAGER_BUNDLED_PLUGIN_DEPS: "1",
HOME: "/tmp/home",
},
extensionsDir,
@@ -677,6 +680,7 @@ describe("bundled plugin postinstall", () => {
runBundledPluginPostinstall({
env: {
OPENCLAW_EAGER_BUNDLED_PLUGIN_DEPS: "1",
npm_config_location: "global",
npm_config_prefix: "/opt/homebrew",
HOME: "/tmp/home",