mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
fix(docker): prune external plugin dist (#77547)
This commit is contained in:
@@ -55,6 +55,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Agents/OpenAI: default direct OpenAI Responses models to the SSE transport instead of WebSocket auto-selection, preventing pi runtime chat turns from hanging on servers where the WebSocket path stalls while the OpenAI HTTP stream works. Thanks @vincentkoc.
|
||||
- Docker: prune package-excluded plugin dist directories from runtime images unless the build explicitly opts that plugin in, so official external plugins such as Feishu stay install-on-demand instead of shipping partial metadata without compiled runtime output. Fixes #77424. Thanks @vincentkoc.
|
||||
- CLI/update: disable and skip plugins that fail package-update plugin sync, so a broken npm/ClawHub/git/marketplace plugin cannot turn a successful OpenClaw package update into a failed update result. Thanks @vincentkoc.
|
||||
- CLI/update: use an absolute POSIX npm script shell during package-manager updates, so restricted PATH environments can still run dependency lifecycle scripts while updating from `--tag main`. Fixes #77530. Thanks @PeterTremonti.
|
||||
- Diagnostics: grant the internal diagnostics event bus to official installed diagnostics exporter plugins, so npm-installed `@openclaw/diagnostics-prometheus` can emit metrics without broadening the capability to arbitrary global plugins. Fixes #76628. Thanks @RayWoo.
|
||||
|
||||
@@ -124,6 +124,7 @@ RUN printf 'packages:\n - .\n - ui\n' > /tmp/pnpm-workspace.runtime.yaml && \
|
||||
cp /tmp/pnpm-workspace.runtime.yaml pnpm-workspace.yaml && \
|
||||
CI=true NPM_CONFIG_FROZEN_LOCKFILE=false pnpm prune --prod && \
|
||||
node scripts/postinstall-bundled-plugins.mjs && \
|
||||
OPENCLAW_EXTENSIONS="$OPENCLAW_EXTENSIONS" node scripts/prune-docker-plugin-dist.mjs && \
|
||||
find dist -type f \( -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' -o -name '*.map' \) -delete && \
|
||||
node scripts/check-package-dist-imports.mjs /app
|
||||
|
||||
|
||||
6
scripts/prune-docker-plugin-dist.d.mts
Normal file
6
scripts/prune-docker-plugin-dist.d.mts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function parseDockerPluginKeepList(value: unknown): Set<string>;
|
||||
export function pruneDockerPluginDist(params?: {
|
||||
cwd?: string;
|
||||
repoRoot?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): string[];
|
||||
52
scripts/prune-docker-plugin-dist.mjs
Normal file
52
scripts/prune-docker-plugin-dist.mjs
Normal file
@@ -0,0 +1,52 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { collectRootPackageExcludedExtensionDirs } from "./lib/bundled-plugin-build-entries.mjs";
|
||||
import { removePathIfExists } from "./runtime-postbuild-shared.mjs";
|
||||
|
||||
function parsePluginList(value) {
|
||||
if (typeof value !== "string") {
|
||||
return new Set();
|
||||
}
|
||||
return new Set(
|
||||
value
|
||||
.split(/[\s,]+/u)
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean),
|
||||
);
|
||||
}
|
||||
|
||||
export function parseDockerPluginKeepList(value) {
|
||||
return parsePluginList(value);
|
||||
}
|
||||
|
||||
export function pruneDockerPluginDist(params = {}) {
|
||||
const repoRoot = params.cwd ?? params.repoRoot ?? process.cwd();
|
||||
const env = params.env ?? process.env;
|
||||
const keepPluginIds = parseDockerPluginKeepList(env.OPENCLAW_EXTENSIONS);
|
||||
const excludedPluginIds = collectRootPackageExcludedExtensionDirs({ cwd: repoRoot });
|
||||
const removed = [];
|
||||
|
||||
for (const pluginId of [...excludedPluginIds].toSorted((left, right) =>
|
||||
left.localeCompare(right),
|
||||
)) {
|
||||
if (keepPluginIds.has(pluginId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const root of ["dist", "dist-runtime"]) {
|
||||
const pluginDistDir = path.join(repoRoot, root, "extensions", pluginId);
|
||||
if (!fs.existsSync(pluginDistDir)) {
|
||||
continue;
|
||||
}
|
||||
removePathIfExists(pluginDistDir);
|
||||
removed.push(path.relative(repoRoot, pluginDistDir).replaceAll("\\", "/"));
|
||||
}
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
||||
pruneDockerPluginDist();
|
||||
}
|
||||
@@ -111,6 +111,9 @@ describe("Dockerfile", () => {
|
||||
expect(dockerfile).toContain("pnpm-workspace.runtime.yaml");
|
||||
expect(dockerfile).toContain(" - ui\\n");
|
||||
expect(dockerfile).toContain("CI=true NPM_CONFIG_FROZEN_LOCKFILE=false pnpm prune --prod");
|
||||
expect(dockerfile).toContain(
|
||||
'OPENCLAW_EXTENSIONS="$OPENCLAW_EXTENSIONS" node scripts/prune-docker-plugin-dist.mjs',
|
||||
);
|
||||
expect(dockerfile).toContain("prune must not rediscover unrelated workspaces");
|
||||
expect(dockerfile).not.toContain(
|
||||
`npm install --prefix "${BUNDLED_PLUGIN_ROOT_DIR}/$ext" --omit=dev --silent`,
|
||||
|
||||
56
src/plugins/prune-docker-plugin-dist.test.ts
Normal file
56
src/plugins/prune-docker-plugin-dist.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
parseDockerPluginKeepList,
|
||||
pruneDockerPluginDist,
|
||||
} from "../../scripts/prune-docker-plugin-dist.mjs";
|
||||
import { cleanupTempDirs, makeTempRepoRoot, writeJsonFile } from "../../test/helpers/temp-repo.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
function makeRepoRoot(prefix: string): string {
|
||||
return makeTempRepoRoot(tempDirs, prefix);
|
||||
}
|
||||
|
||||
function writeDistPluginFile(repoRoot: string, root: "dist" | "dist-runtime", pluginId: string) {
|
||||
const pluginDir = path.join(repoRoot, root, "extensions", pluginId);
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(pluginDir, "openclaw.plugin.json"), "{}\n", "utf8");
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanupTempDirs(tempDirs);
|
||||
});
|
||||
|
||||
describe("pruneDockerPluginDist", () => {
|
||||
it("parses space and comma separated Docker plugin keep lists", () => {
|
||||
expect([...parseDockerPluginKeepList("diagnostics-otel feishu,discord")]).toEqual([
|
||||
"diagnostics-otel",
|
||||
"feishu",
|
||||
"discord",
|
||||
]);
|
||||
});
|
||||
|
||||
it("removes package-excluded plugin dist unless Docker explicitly opts it in", () => {
|
||||
const repoRoot = makeRepoRoot("openclaw-docker-plugin-dist-");
|
||||
writeJsonFile(path.join(repoRoot, "package.json"), {
|
||||
files: ["dist/**", "!dist/extensions/diagnostics-otel/**", "!dist/extensions/feishu/**"],
|
||||
});
|
||||
writeDistPluginFile(repoRoot, "dist", "diagnostics-otel");
|
||||
writeDistPluginFile(repoRoot, "dist", "feishu");
|
||||
writeDistPluginFile(repoRoot, "dist-runtime", "feishu");
|
||||
writeDistPluginFile(repoRoot, "dist", "telegram");
|
||||
|
||||
const removed = pruneDockerPluginDist({
|
||||
repoRoot,
|
||||
env: { OPENCLAW_EXTENSIONS: "diagnostics-otel" } as NodeJS.ProcessEnv,
|
||||
});
|
||||
|
||||
expect(removed).toEqual(["dist/extensions/feishu", "dist-runtime/extensions/feishu"]);
|
||||
expect(fs.existsSync(path.join(repoRoot, "dist", "extensions", "diagnostics-otel"))).toBe(true);
|
||||
expect(fs.existsSync(path.join(repoRoot, "dist", "extensions", "feishu"))).toBe(false);
|
||||
expect(fs.existsSync(path.join(repoRoot, "dist-runtime", "extensions", "feishu"))).toBe(false);
|
||||
expect(fs.existsSync(path.join(repoRoot, "dist", "extensions", "telegram"))).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user