fix: use declarative channel configured-state probes

This commit is contained in:
Peter Steinberger
2026-05-02 01:06:33 +01:00
parent 3e3d7a82a4
commit 002c1d9c35
8 changed files with 97 additions and 2 deletions

View File

@@ -44,6 +44,11 @@
"nativeSkillsAutoEnabled": true
},
"configuredState": {
"env": {
"allOf": [
"DISCORD_BOT_TOKEN"
]
},
"specifier": "./configured-state",
"exportName": "hasDiscordConfiguredState"
}

View File

@@ -27,6 +27,12 @@
],
"systemImage": "network",
"configuredState": {
"env": {
"allOf": [
"IRC_HOST",
"IRC_NICK"
]
},
"specifier": "./configured-state",
"exportName": "hasIrcConfiguredState"
}

View File

@@ -33,6 +33,13 @@
"nativeSkillsAutoEnabled": false
},
"configuredState": {
"env": {
"anyOf": [
"SLACK_APP_TOKEN",
"SLACK_BOT_TOKEN",
"SLACK_USER_TOKEN"
]
},
"specifier": "./configured-state",
"exportName": "hasSlackConfiguredState"
}

View File

@@ -43,6 +43,11 @@
"nativeSkillsAutoEnabled": true
},
"configuredState": {
"env": {
"allOf": [
"TELEGRAM_BOT_TOKEN"
]
},
"specifier": "./configured-state",
"exportName": "hasTelegramConfiguredState"
}

View File

@@ -1,9 +1,12 @@
import { createRequire } from "node:module";
import { describe, expect, it } from "vitest";
import {
hasBundledChannelConfiguredState,
listBundledChannelIdsWithConfiguredState,
} from "./configured-state.js";
const nodeRequire = createRequire(import.meta.url);
describe("bundled channel configured-state metadata", () => {
it("lists the shipped metadata-first configured-state channels", () => {
expect(listBundledChannelIdsWithConfiguredState()).toEqual(
@@ -41,4 +44,22 @@ describe("bundled channel configured-state metadata", () => {
}),
).toBe(true);
});
it("uses declarative env metadata without a TypeScript source require hook", () => {
const previousTsHook = nodeRequire.extensions[".ts"];
delete nodeRequire.extensions[".ts"];
try {
expect(
hasBundledChannelConfiguredState({
channelId: "discord",
cfg: {},
env: { DISCORD_BOT_TOKEN: "token" },
}),
).toBe(true);
} finally {
if (previousTsHook) {
nodeRequire.extensions[".ts"] = previousTsHook;
}
}
});
});

View File

@@ -16,12 +16,29 @@ type ChannelPackageStateChecker = (params: {
type ChannelPackageStateMetadata = {
specifier?: string;
exportName?: string;
env?: {
allOf?: readonly string[];
anyOf?: readonly string[];
};
};
export type ChannelPackageStateMetadataKey = "configuredState" | "persistedAuthState";
const log = createSubsystemLogger("channels");
function normalizeStringList(value: unknown): string[] {
if (!Array.isArray(value)) {
return [];
}
return value
.map((entry) => normalizeOptionalString(entry))
.filter((entry): entry is string => Boolean(entry));
}
function hasNonEmptyEnvValue(env: NodeJS.ProcessEnv | undefined, key: string): boolean {
return typeof env?.[key] === "string" && env[key].trim().length > 0;
}
function resolveChannelPackageStateMetadata(
entry: PluginChannelCatalogEntry,
metadataKey: ChannelPackageStateMetadataKey,
@@ -32,10 +49,17 @@ function resolveChannelPackageStateMetadata(
}
const specifier = normalizeOptionalString(metadata.specifier) ?? "";
const exportName = normalizeOptionalString(metadata.exportName) ?? "";
if (!specifier || !exportName) {
const allOf = normalizeStringList(metadata.env?.allOf);
const anyOf = normalizeStringList(metadata.env?.anyOf);
const env = allOf.length > 0 || anyOf.length > 0 ? { allOf, anyOf } : undefined;
if ((!specifier || !exportName) && !env) {
return null;
}
return { specifier, exportName };
return {
...(specifier ? { specifier } : {}),
...(exportName ? { exportName } : {}),
...(env ? { env } : {}),
};
}
function listChannelPackageStateCatalog(
@@ -55,6 +79,17 @@ function resolveChannelPackageStateChecker(params: {
return null;
}
if (metadata.env) {
return ({ env }) => {
const allOf = metadata.env?.allOf ?? [];
const anyOf = metadata.env?.anyOf ?? [];
return (
(allOf.length === 0 || allOf.every((key) => hasNonEmptyEnvValue(env, key))) &&
(anyOf.length === 0 || anyOf.some((key) => hasNonEmptyEnvValue(env, key)))
);
};
}
try {
const moduleExport = loadChannelPluginModule({
modulePath: resolveExistingPluginModulePath(params.entry.rootDir, metadata.specifier!),

View File

@@ -325,6 +325,9 @@ describe("bundled plugin metadata", () => {
{
dir: "discord",
configuredState: {
env: {
allOf: ["DISCORD_BOT_TOKEN"],
},
specifier: "./configured-state",
exportName: "hasDiscordConfiguredState",
},
@@ -332,6 +335,9 @@ describe("bundled plugin metadata", () => {
{
dir: "irc",
configuredState: {
env: {
allOf: ["IRC_HOST", "IRC_NICK"],
},
specifier: "./configured-state",
exportName: "hasIrcConfiguredState",
},
@@ -339,6 +345,9 @@ describe("bundled plugin metadata", () => {
{
dir: "slack",
configuredState: {
env: {
anyOf: ["SLACK_APP_TOKEN", "SLACK_BOT_TOKEN", "SLACK_USER_TOKEN"],
},
specifier: "./configured-state",
exportName: "hasSlackConfiguredState",
},
@@ -346,6 +355,9 @@ describe("bundled plugin metadata", () => {
{
dir: "telegram",
configuredState: {
env: {
allOf: ["TELEGRAM_BOT_TOKEN"],
},
specifier: "./configured-state",
exportName: "hasTelegramConfiguredState",
},

View File

@@ -1481,6 +1481,10 @@ export type PluginPackageChannel = {
configuredState?: {
specifier?: string;
exportName?: string;
env?: {
allOf?: readonly string[];
anyOf?: readonly string[];
};
};
persistedAuthState?: {
specifier?: string;