test(channels): cover staged setup entry dependency loading

This commit is contained in:
Peter Steinberger
2026-04-24 06:09:46 +01:00
parent b642d7343a
commit 6601eef58b
3 changed files with 100 additions and 35 deletions

View File

@@ -0,0 +1,14 @@
import { describe, expect, it, vi } from "vitest";
vi.mock("@whiskeysockets/baileys", () => {
throw new Error("setup plugin load must not load Baileys");
});
describe("whatsapp setup entry", () => {
it("loads the setup plugin without installing or importing runtime dependencies", async () => {
const { default: setupEntry } = await import("./setup-entry.js");
expect(setupEntry.kind).toBe("bundled-channel-setup-entry");
expect(setupEntry.loadSetupPlugin({ installRuntimeDeps: false }).id).toBe("whatsapp");
});
});

View File

@@ -585,8 +585,10 @@ export OPENCLAW_NO_ONBOARD=1
export OPENCLAW_PLUGIN_STAGE_DIR="$HOME/.openclaw/plugin-runtime-deps"
mkdir -p "$OPENCLAW_PLUGIN_STAGE_DIR"
CHANNEL="feishu"
DEP_SENTINEL="@larksuiteoapi/node-sdk"
declare -A SETUP_ENTRY_DEP_SENTINELS=(
[feishu]="@larksuiteoapi/node-sdk"
[whatsapp]="@whiskeysockets/baileys"
)
package_root() {
printf "%s/openclaw" "$(npm root -g)"
@@ -597,18 +599,21 @@ package_tgz="${OPENCLAW_CURRENT_PACKAGE_TGZ:?missing OPENCLAW_CURRENT_PACKAGE_TG
npm install -g "$package_tgz" --no-fund --no-audit >/tmp/openclaw-setup-entry-install.log 2>&1
root="$(package_root)"
test -d "$root/dist/extensions/$CHANNEL"
if [ -d "$root/dist/extensions/$CHANNEL/node_modules" ]; then
echo "$CHANNEL runtime deps should not be preinstalled in package" >&2
find "$root/dist/extensions/$CHANNEL/node_modules" -maxdepth 3 -type f | head -40 >&2 || true
exit 1
fi
if [ -f "$root/node_modules/$DEP_SENTINEL/package.json" ]; then
echo "$DEP_SENTINEL should not be installed at package root before setup-entry load" >&2
exit 1
fi
for channel in "${!SETUP_ENTRY_DEP_SENTINELS[@]}"; do
dep_sentinel="${SETUP_ENTRY_DEP_SENTINELS[$channel]}"
test -d "$root/dist/extensions/$channel"
if [ -d "$root/dist/extensions/$channel/node_modules" ]; then
echo "$channel runtime deps should not be preinstalled in package" >&2
find "$root/dist/extensions/$channel/node_modules" -maxdepth 3 -type f | head -40 >&2 || true
exit 1
fi
if [ -f "$root/node_modules/$dep_sentinel/package.json" ]; then
echo "$dep_sentinel should not be installed at package root before setup-entry load" >&2
exit 1
fi
done
echo "Probing real Feishu bundled setup entry before channel configuration..."
echo "Probing real bundled setup entries before channel configuration..."
(
cd "$root"
node --input-type=module - <<'NODE'
@@ -633,22 +638,33 @@ const setupPluginLoader = Object.values(bundled).find(
if (!setupPluginLoader) {
throw new Error("missing packaged getBundledChannelSetupPlugin export");
}
const plugin = setupPluginLoader("feishu");
console.log(plugin ? "Feishu setup plugin loaded pre-config" : "Feishu setup plugin deferred pre-config");
for (const channel of ["feishu", "whatsapp"]) {
const plugin = setupPluginLoader(channel);
if (!plugin) {
throw new Error(`${channel} setup plugin did not load pre-config`);
}
if (plugin.id !== channel) {
throw new Error(`${channel} setup plugin id mismatch: ${plugin.id}`);
}
console.log(`${channel} setup plugin loaded pre-config`);
}
NODE
)
if [ -e "$root/dist/extensions/$CHANNEL/node_modules/$DEP_SENTINEL/package.json" ]; then
echo "setup-entry discovery installed deps into bundled plugin tree before channel configuration" >&2
exit 1
fi
if find "$OPENCLAW_PLUGIN_STAGE_DIR" -maxdepth 12 -path "*/node_modules/$DEP_SENTINEL/package.json" -type f | grep -q .; then
echo "setup-entry discovery installed external staged deps before channel configuration" >&2
find "$OPENCLAW_PLUGIN_STAGE_DIR" -maxdepth 12 -type f | sort | head -160 >&2 || true
exit 1
fi
for channel in "${!SETUP_ENTRY_DEP_SENTINELS[@]}"; do
dep_sentinel="${SETUP_ENTRY_DEP_SENTINELS[$channel]}"
if [ -e "$root/dist/extensions/$channel/node_modules/$dep_sentinel/package.json" ]; then
echo "setup-entry discovery installed $channel deps into bundled plugin tree before channel configuration" >&2
exit 1
fi
if find "$OPENCLAW_PLUGIN_STAGE_DIR" -maxdepth 12 -path "*/node_modules/$dep_sentinel/package.json" -type f | grep -q .; then
echo "setup-entry discovery installed $channel external staged deps before channel configuration" >&2
find "$OPENCLAW_PLUGIN_STAGE_DIR" -maxdepth 12 -type f | sort | head -160 >&2 || true
exit 1
fi
done
echo "Configuring Feishu; doctor should now install bundled runtime deps externally..."
echo "Configuring setup-entry channels; doctor should now install bundled runtime deps externally..."
node - <<'NODE'
const fs = require("node:fs");
const path = require("node:path");
@@ -669,6 +685,10 @@ config.channels = {
...(config.channels?.feishu || {}),
enabled: true,
},
whatsapp: {
...(config.channels?.whatsapp || {}),
enabled: true,
},
};
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
@@ -676,16 +696,19 @@ NODE
openclaw doctor --non-interactive >/tmp/openclaw-setup-entry-doctor.log 2>&1
if [ -e "$root/dist/extensions/$CHANNEL/node_modules/$DEP_SENTINEL/package.json" ]; then
echo "expected configured Feishu deps to be installed externally, not into bundled plugin tree" >&2
exit 1
fi
if ! find "$OPENCLAW_PLUGIN_STAGE_DIR" -maxdepth 12 -path "*/node_modules/$DEP_SENTINEL/package.json" -type f | grep -q .; then
echo "missing external staged dependency sentinel for configured $CHANNEL: $DEP_SENTINEL" >&2
cat /tmp/openclaw-setup-entry-doctor.log >&2
find "$OPENCLAW_PLUGIN_STAGE_DIR" -maxdepth 12 -type f | sort | head -160 >&2 || true
exit 1
fi
for channel in "${!SETUP_ENTRY_DEP_SENTINELS[@]}"; do
dep_sentinel="${SETUP_ENTRY_DEP_SENTINELS[$channel]}"
if [ -e "$root/dist/extensions/$channel/node_modules/$dep_sentinel/package.json" ]; then
echo "expected configured $channel deps to be installed externally, not into bundled plugin tree" >&2
exit 1
fi
if ! find "$OPENCLAW_PLUGIN_STAGE_DIR" -maxdepth 12 -path "*/node_modules/$dep_sentinel/package.json" -type f | grep -q .; then
echo "missing external staged dependency sentinel for configured $channel: $dep_sentinel" >&2
cat /tmp/openclaw-setup-entry-doctor.log >&2
find "$OPENCLAW_PLUGIN_STAGE_DIR" -maxdepth 12 -type f | sort | head -160 >&2 || true
exit 1
fi
done
echo "bundled channel setup-entry runtime deps Docker E2E passed"
EOF

View File

@@ -763,6 +763,34 @@ describe("bundled channel entry shape guards", () => {
expect(offenders).toEqual([]);
});
it("keeps staged runtime-dependency setup entries on setup-only plugin barrels", () => {
const offenders: string[] = [];
for (const extensionDir of bundledPluginRoots) {
const setupEntryPath = path.join(extensionDir, "setup-entry.ts");
const packageJsonPath = path.join(extensionDir, "package.json");
if (!fs.existsSync(setupEntryPath) || !fs.existsSync(packageJsonPath)) {
continue;
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
openclaw?: {
bundle?: {
stageRuntimeDependencies?: boolean;
};
};
};
if (packageJson.openclaw?.bundle?.stageRuntimeDependencies !== true) {
continue;
}
const setupEntrySource = fs.readFileSync(setupEntryPath, "utf8");
if (/specifier:\s*["']\.\/(?:api|channel-plugin-api)\.js["']/u.test(setupEntrySource)) {
offenders.push(path.relative(process.cwd(), setupEntryPath));
}
}
expect(offenders).toEqual([]);
});
it("keeps bundled channel entrypoints free of static src imports", () => {
const offenders = collectBundledChannelEntrypointOffenders(bundledPluginRoots, (source) =>
/^(?:import|export)\s.+["']\.\/src\//mu.test(source),