Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
304 lines
11 KiB
TypeScript
304 lines
11 KiB
TypeScript
// Bundled Plugin Build Entries tests cover bundled plugin build entries script behavior.
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
collectRootPackageExcludedExtensionDirs,
|
|
listBundledPluginBuildEntries,
|
|
listBundledPluginPackArtifacts,
|
|
} from "../../scripts/lib/bundled-plugin-build-entries.mjs";
|
|
import { expectNoNodeFsScans } from "../../src/test-utils/fs-scan-assertions.js";
|
|
|
|
function expectNoPrefixMatches(values: string[], prefix: string) {
|
|
expect(values.filter((value) => value.startsWith(prefix))).toEqual([]);
|
|
}
|
|
|
|
function expectSomePrefixMatch(values: string[], prefix: string) {
|
|
expect(values.filter((value) => value.startsWith(prefix))).not.toEqual([]);
|
|
}
|
|
|
|
function pickEntries(entries: Record<string, string>, keys: readonly string[]) {
|
|
return Object.fromEntries(keys.map((key) => [key, entries[key]]));
|
|
}
|
|
|
|
describe("bundled plugin build entries", () => {
|
|
const bundledChannelEntrySources = ["index.ts", "channel-entry.ts", "setup-entry.ts"];
|
|
const forEachBundledChannelEntry = (
|
|
visit: (params: { entryPath: string; entry: string; pluginId: string }) => void,
|
|
) => {
|
|
for (const dirent of fs.readdirSync("extensions", { withFileTypes: true })) {
|
|
if (!dirent.isDirectory()) {
|
|
continue;
|
|
}
|
|
|
|
for (const sourceEntry of bundledChannelEntrySources) {
|
|
const entryPath = path.join("extensions", dirent.name, sourceEntry);
|
|
if (!fs.existsSync(entryPath)) {
|
|
continue;
|
|
}
|
|
visit({
|
|
entryPath,
|
|
entry: fs.readFileSync(entryPath, "utf8"),
|
|
pluginId: dirent.name,
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
it("includes manifest-less runtime core support packages in dist build entries", () => {
|
|
const entries = listBundledPluginBuildEntries();
|
|
const expectedEntries = {
|
|
"extensions/image-generation-core/api": "extensions/image-generation-core/api.ts",
|
|
"extensions/image-generation-core/runtime-api":
|
|
"extensions/image-generation-core/runtime-api.ts",
|
|
"extensions/media-understanding-core/runtime-api":
|
|
"extensions/media-understanding-core/runtime-api.ts",
|
|
};
|
|
|
|
expect(pickEntries(entries, Object.keys(expectedEntries))).toStrictEqual(expectedEntries);
|
|
});
|
|
|
|
it("keeps the Matrix packaged runtime shim in bundled plugin build entries", () => {
|
|
const entries = listBundledPluginBuildEntries();
|
|
const expectedEntries = {
|
|
"extensions/matrix/plugin-entry.handlers.runtime":
|
|
"extensions/matrix/plugin-entry.handlers.runtime.ts",
|
|
};
|
|
|
|
expect(pickEntries(entries, Object.keys(expectedEntries))).toStrictEqual(expectedEntries);
|
|
});
|
|
|
|
it("filters bundled plugin build entries for bounded script lanes", () => {
|
|
const entries = listBundledPluginBuildEntries({
|
|
env: {
|
|
...process.env,
|
|
OPENCLAW_BUNDLED_PLUGIN_BUILD_IDS: "active-memory,acpx",
|
|
},
|
|
});
|
|
const entryKeys = Object.keys(entries);
|
|
|
|
expect(entryKeys).toEqual(expect.arrayContaining(["extensions/acpx/index"]));
|
|
expect(entryKeys.every((entry) => /^extensions\/(?:acpx|active-memory)\//u.test(entry))).toBe(
|
|
true,
|
|
);
|
|
});
|
|
|
|
it("rejects unknown bounded bundled plugin build ids", () => {
|
|
expect(() =>
|
|
listBundledPluginBuildEntries({
|
|
env: {
|
|
...process.env,
|
|
OPENCLAW_BUNDLED_PLUGIN_BUILD_IDS: "missing-plugin",
|
|
},
|
|
}),
|
|
).toThrow(
|
|
"OPENCLAW_BUNDLED_PLUGIN_BUILD_IDS references unknown bundled plugin id(s): missing-plugin",
|
|
);
|
|
});
|
|
|
|
it("keeps the Telegram ingress worker out of bundled plugin public-surface entries", () => {
|
|
const entries = listBundledPluginBuildEntries();
|
|
|
|
expect(entries["extensions/telegram/telegram-ingress-worker.runtime"]).toBeUndefined();
|
|
});
|
|
|
|
it("keeps top-level bundled plugin test helpers out of public-surface entries", () => {
|
|
const entries = listBundledPluginBuildEntries();
|
|
|
|
expect(entries["extensions/browser/test-support"]).toBeUndefined();
|
|
expect(entries["extensions/comfy/test-helpers"]).toBeUndefined();
|
|
expect(entries["extensions/minimax/provider-http.test-helpers"]).toBeUndefined();
|
|
});
|
|
|
|
it("discovers repo plugin build entries without directory scans", () => {
|
|
const payload = expectNoNodeFsScans<{
|
|
artifacts: number;
|
|
entries: number;
|
|
}>(
|
|
`
|
|
const build = await import("./scripts/lib/bundled-plugin-build-entries.mjs");
|
|
const entries = build.listBundledPluginBuildEntries();
|
|
const artifacts = build.listBundledPluginPackArtifacts();
|
|
return {
|
|
artifacts: artifacts.length,
|
|
entries: Object.keys(entries).length,
|
|
};
|
|
`,
|
|
{ counters: ["readdirSync"] },
|
|
);
|
|
|
|
expect(payload.entries).toBeGreaterThan(0);
|
|
expect(payload.artifacts).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("packs runtime core support packages without requiring plugin manifests", () => {
|
|
const artifacts = listBundledPluginPackArtifacts();
|
|
|
|
expect(artifacts).toContain("dist/extensions/image-generation-core/package.json");
|
|
expect(artifacts).toContain("dist/extensions/image-generation-core/runtime-api.js");
|
|
expect(artifacts).not.toContain("dist/extensions/image-generation-core/openclaw.plugin.json");
|
|
expect(artifacts).toContain("dist/extensions/media-understanding-core/runtime-api.js");
|
|
expect(artifacts).not.toContain(
|
|
"dist/extensions/media-understanding-core/openclaw.plugin.json",
|
|
);
|
|
});
|
|
|
|
it("packs the Matrix packaged runtime shim", () => {
|
|
const artifacts = listBundledPluginPackArtifacts({ includeRootPackageExcludedDirs: true });
|
|
|
|
expect(artifacts).toContain("dist/extensions/matrix/plugin-entry.handlers.runtime.js");
|
|
});
|
|
|
|
it("keeps private QA bundles out of required npm pack artifacts", () => {
|
|
const artifacts = listBundledPluginPackArtifacts();
|
|
|
|
expectNoPrefixMatches(artifacts, "dist/extensions/qa-channel/");
|
|
expectNoPrefixMatches(artifacts, "dist/extensions/qa-lab/");
|
|
expectNoPrefixMatches(artifacts, "dist/extensions/qa-matrix/");
|
|
});
|
|
|
|
it("keeps explicitly downloadable plugins out of bundled package artifacts", () => {
|
|
const entries = listBundledPluginBuildEntries();
|
|
const artifacts = listBundledPluginPackArtifacts();
|
|
|
|
for (const pluginId of ["acpx", "googlechat", "line"]) {
|
|
expectSomePrefixMatch(Object.keys(entries), `extensions/${pluginId}/`);
|
|
expectNoPrefixMatches(artifacts, `dist/extensions/${pluginId}/`);
|
|
}
|
|
for (const pluginId of ["qqbot", "whatsapp"]) {
|
|
expectNoPrefixMatches(Object.keys(entries), `extensions/${pluginId}/`);
|
|
expectNoPrefixMatches(artifacts, `dist/extensions/${pluginId}/`);
|
|
}
|
|
});
|
|
|
|
it("keeps external-only providers out of bundled dist entries", () => {
|
|
const entries = listBundledPluginBuildEntries();
|
|
const artifacts = listBundledPluginPackArtifacts();
|
|
|
|
for (const pluginId of ["amazon-bedrock", "amazon-bedrock-mantle", "anthropic-vertex"]) {
|
|
expectNoPrefixMatches(Object.keys(entries), `extensions/${pluginId}/`);
|
|
expectNoPrefixMatches(artifacts, `dist/extensions/${pluginId}/`);
|
|
}
|
|
});
|
|
|
|
it("keeps externalized runtime-dependency plugins out of bundled dist entries", () => {
|
|
const entries = listBundledPluginBuildEntries();
|
|
const artifacts = listBundledPluginPackArtifacts();
|
|
|
|
for (const pluginId of ["copilot", "openshell", "slack", "tokenjuice"]) {
|
|
expectNoPrefixMatches(Object.keys(entries), `extensions/${pluginId}/`);
|
|
expectNoPrefixMatches(artifacts, `dist/extensions/${pluginId}/`);
|
|
}
|
|
});
|
|
|
|
it("keeps Cohere bundled through the externalization transition", () => {
|
|
const artifacts = listBundledPluginPackArtifacts();
|
|
|
|
expect(artifacts).toContain("dist/extensions/cohere/index.js");
|
|
expect(artifacts).toContain("dist/extensions/cohere/openclaw.plugin.json");
|
|
expect(artifacts).toContain("dist/extensions/cohere/package.json");
|
|
});
|
|
|
|
it("keeps bundled channel secret contracts on packed top-level sidecars", () => {
|
|
const artifacts = listBundledPluginPackArtifacts();
|
|
const excludedPackageDirs = collectRootPackageExcludedExtensionDirs();
|
|
const offenders: string[] = [];
|
|
const secretBackedPluginIds = new Set<string>();
|
|
|
|
forEachBundledChannelEntry(({ entryPath, entry, pluginId }) => {
|
|
if (!entry.includes('exportName: "channelSecrets"')) {
|
|
return;
|
|
}
|
|
secretBackedPluginIds.add(pluginId);
|
|
if (entry.includes("./src/secret-contract.js")) {
|
|
offenders.push(entryPath);
|
|
}
|
|
expect(entry).toContain('specifier: "./secret-contract-api.js"');
|
|
});
|
|
|
|
expect(offenders).toStrictEqual([]);
|
|
|
|
for (const pluginId of [...secretBackedPluginIds].toSorted()) {
|
|
if (excludedPackageDirs.has(pluginId)) {
|
|
continue;
|
|
}
|
|
const secretApiPath = path.join("extensions", pluginId, "secret-contract-api.ts");
|
|
expect(fs.readFileSync(secretApiPath, "utf8")).toContain("channelSecrets");
|
|
expect(artifacts).toContain(`dist/extensions/${pluginId}/secret-contract-api.js`);
|
|
}
|
|
});
|
|
|
|
it("keeps dedicated channel contract exports off broad contract-api sidecars", () => {
|
|
const duplicateExportMarkersByArtifact = {
|
|
"directory-contract-api.ts": [
|
|
"DirectoryContractPlugin",
|
|
"DirectoryGroupsFromConfig",
|
|
"DirectoryPeersFromConfig",
|
|
],
|
|
"doctor-contract-api.ts": [
|
|
"legacyConfigRules",
|
|
"normalizeCompatibilityConfig",
|
|
"stateMigrations",
|
|
],
|
|
"secret-contract-api.ts": [
|
|
"channelSecrets",
|
|
"collectRuntimeConfigAssignments",
|
|
"secretTargetRegistryEntries",
|
|
],
|
|
"security-audit-contract-api.ts": ["SecurityAuditFindings"],
|
|
"security-contract-api.ts": [
|
|
"collectUnsupportedSecretRefConfigCandidates",
|
|
"unsupportedSecretRefSurfacePatterns",
|
|
],
|
|
"session-binding-contract-api.ts": [
|
|
"ConversationBindingManager",
|
|
"ThreadBindingManager",
|
|
"ThreadBindingsForTests",
|
|
"setMatrixRuntime",
|
|
],
|
|
} as const;
|
|
const offenders: string[] = [];
|
|
|
|
for (const dirent of fs.readdirSync("extensions", { withFileTypes: true })) {
|
|
if (!dirent.isDirectory()) {
|
|
continue;
|
|
}
|
|
const contractApiPath = path.join("extensions", dirent.name, "contract-api.ts");
|
|
if (!fs.existsSync(contractApiPath)) {
|
|
continue;
|
|
}
|
|
const contractApi = fs.readFileSync(contractApiPath, "utf8");
|
|
for (const [artifact, markers] of Object.entries(duplicateExportMarkersByArtifact)) {
|
|
if (!fs.existsSync(path.join("extensions", dirent.name, artifact))) {
|
|
continue;
|
|
}
|
|
for (const marker of markers) {
|
|
if (contractApi.includes(marker)) {
|
|
offenders.push(`${contractApiPath} duplicates ${artifact}: ${marker}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
expect(offenders).toStrictEqual([]);
|
|
});
|
|
|
|
it("keeps bundled channel entry metadata on packed top-level sidecars", () => {
|
|
const offenders: string[] = [];
|
|
|
|
forEachBundledChannelEntry(({ entryPath, entry }) => {
|
|
if (
|
|
!entry.includes("defineBundledChannelEntry") &&
|
|
!entry.includes("defineBundledChannelSetupEntry")
|
|
) {
|
|
return;
|
|
}
|
|
if (/specifier:\s*["']\.\/src\//u.test(entry)) {
|
|
offenders.push(entryPath);
|
|
}
|
|
});
|
|
|
|
expect(offenders).toStrictEqual([]);
|
|
});
|
|
});
|