refactor: gate setup promotion by manifest feature

This commit is contained in:
Peter Steinberger
2026-04-22 07:07:08 +01:00
parent 866a120d69
commit 4fbd314e1f
6 changed files with 35 additions and 7 deletions

View File

@@ -30,6 +30,9 @@
"./index.ts"
],
"setupEntry": "./setup-entry.ts",
"setupFeatures": {
"configPromotion": true
},
"channel": {
"id": "matrix",
"label": "Matrix",

View File

@@ -20,6 +20,7 @@
],
"setupEntry": "./setup-entry.ts",
"setupFeatures": {
"configPromotion": true,
"legacyStateMigrations": true
},
"channel": {

View File

@@ -50,6 +50,11 @@ type BundledChannelSetupEntryRuntimeContract = {
};
};
type BundledChannelPackageSetupFeature =
| "configPromotion"
| "legacyStateMigrations"
| "legacySessionSurfaces";
type GeneratedBundledChannelEntry = {
id: string;
entry: BundledChannelEntryRuntimeContract;
@@ -507,6 +512,16 @@ export function listBundledChannelPluginIds(): readonly ChannelId[] {
return listBundledChannelPluginIdsForRoot(resolveBundledChannelRootScope());
}
export function hasBundledChannelPackageSetupFeature(
id: ChannelId,
feature: BundledChannelPackageSetupFeature,
): boolean {
const rootScope = resolveBundledChannelRootScope();
return (
resolveBundledChannelMetadata(id, rootScope)?.packageManifest?.setupFeatures?.[feature] === true
);
}
function resolveBundledChannelMetadata(
id: ChannelId,
rootScope: BundledChannelRootScope,

View File

@@ -1,10 +1,12 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const getBundledChannelPluginMock = vi.hoisted(() => vi.fn());
const hasBundledChannelPackageSetupFeatureMock = vi.hoisted(() => vi.fn());
const getLoadedChannelPluginMock = vi.hoisted(() => vi.fn());
vi.mock("./bundled.js", () => ({
getBundledChannelPlugin: getBundledChannelPluginMock,
hasBundledChannelPackageSetupFeature: hasBundledChannelPackageSetupFeatureMock,
}));
vi.mock("./registry.js", () => ({
@@ -19,6 +21,8 @@ import {
describe("setup promotion helpers", () => {
beforeEach(() => {
getBundledChannelPluginMock.mockReset();
hasBundledChannelPackageSetupFeatureMock.mockReset();
hasBundledChannelPackageSetupFeatureMock.mockReturnValue(false);
getLoadedChannelPluginMock.mockReset();
});
@@ -38,9 +42,9 @@ describe("setup promotion helpers", () => {
expect(getBundledChannelPluginMock).not.toHaveBeenCalled();
});
it("keeps WhatsApp static promotion cheap even when named accounts already exist", () => {
it("skips bundled setup promotion without a manifest feature", () => {
const keys = resolveSingleAccountKeysToMove({
channelKey: "whatsapp",
channelKey: "demo",
channel: {
accounts: {
work: { enabled: true },
@@ -53,11 +57,16 @@ describe("setup promotion helpers", () => {
});
expect(keys).toEqual(["dmPolicy", "allowFrom", "groupPolicy", "groupAllowFrom"]);
expect(getLoadedChannelPluginMock).toHaveBeenCalledWith("whatsapp");
expect(getLoadedChannelPluginMock).toHaveBeenCalledWith("demo");
expect(hasBundledChannelPackageSetupFeatureMock).toHaveBeenCalledWith(
"demo",
"configPromotion",
);
expect(getBundledChannelPluginMock).not.toHaveBeenCalled();
});
it("loads bundled setup only for non-static migration keys", () => {
hasBundledChannelPackageSetupFeatureMock.mockReturnValue(true);
getBundledChannelPluginMock.mockReturnValue({
setup: {
singleAccountKeysToMove: ["customAuth"],
@@ -96,6 +105,7 @@ describe("setup promotion helpers", () => {
});
it("loads bundled setup for named-account filters before registry bootstrap", () => {
hasBundledChannelPackageSetupFeatureMock.mockReturnValue(true);
getBundledChannelPluginMock.mockReturnValue({
setup: {
namedAccountPromotionKeys: ["token"],

View File

@@ -1,6 +1,6 @@
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { getBundledChannelPlugin } from "./bundled.js";
import { getBundledChannelPlugin, hasBundledChannelPackageSetupFeature } from "./bundled.js";
import { getLoadedChannelPlugin } from "./registry.js";
type ChannelSectionBase = {
@@ -49,8 +49,6 @@ type ChannelSetupPromotionSurface = {
}) => string | undefined;
};
const BUNDLED_CHANNELS_WITHOUT_SETUP_PROMOTION_SURFACE = new Set(["whatsapp"]);
function asPromotionSurface(setup: unknown): ChannelSetupPromotionSurface | null {
return setup && typeof setup === "object" ? (setup as ChannelSetupPromotionSurface) : null;
}
@@ -64,7 +62,7 @@ function getLoadedChannelSetupPromotionSurface(
function getBundledChannelSetupPromotionSurface(
channelKey: string,
): ChannelSetupPromotionSurface | null {
if (BUNDLED_CHANNELS_WITHOUT_SETUP_PROMOTION_SURFACE.has(channelKey)) {
if (!hasBundledChannelPackageSetupFeature(channelKey, "configPromotion")) {
return null;
}
return asPromotionSurface(getBundledChannelPlugin(channelKey)?.setup);

View File

@@ -975,6 +975,7 @@ export type OpenClawPackageStartup = {
};
export type OpenClawPackageSetupFeatures = {
configPromotion?: boolean;
legacyStateMigrations?: boolean;
legacySessionSurfaces?: boolean;
};