diff --git a/README.md b/README.md index 9e3c5f2..16b3fb0 100644 --- a/README.md +++ b/README.md @@ -150,10 +150,9 @@ How to run a "hello-world" plugin the Cloudflare way: plugins: - skipBotEvents: true uses: - # hello-world-plugin + # hello-world-plugin - plugin: http://127.0.0.1:9090 runsOn: [ "issue_comment.created" ] - type: github with: response: world ``` diff --git a/src/github/handlers/help-command.ts b/src/github/handlers/help-command.ts index dbf98b5..0acf5db 100644 --- a/src/github/handlers/help-command.ts +++ b/src/github/handlers/help-command.ts @@ -1,27 +1,14 @@ import { getConfig } from "../utils/config"; import { GithubPlugin } from "../types/plugin-configuration"; import { GitHubContext } from "../github-context"; -import { manifestSchema, manifestValidator } from "../../types/manifest"; -import { Value } from "@sinclair/typebox/value"; import { getManifest } from "../utils/plugins"; async function parseCommandsFromManifest(context: GitHubContext<"issue_comment.created">, plugin: string | GithubPlugin) { const commands: string[] = []; const manifest = await getManifest(context, plugin); - if (manifest) { - Value.Default(manifestSchema, manifest); - const errors = manifestValidator.testReturningErrors(manifest); - if (errors !== null) { - console.error(`Failed to load the manifest for ${JSON.stringify(plugin)}`); - for (const error of errors) { - console.error(error); - } - } else { - if (manifest?.commands) { - for (const [key, value] of Object.entries(manifest.commands)) { - commands.push(`| \`/${getContent(key)}\` | ${getContent(value.description)} | \`${getContent(value["ubiquity:example"])}\` |`); - } - } + if (manifest?.commands) { + for (const [key, value] of Object.entries(manifest.commands)) { + commands.push(`| \`/${getContent(key)}\` | ${getContent(value.description)} | \`${getContent(value["ubiquity:example"])}\` |`); } } return commands; diff --git a/src/github/handlers/index.ts b/src/github/handlers/index.ts index c2a78cc..cfbdc93 100644 --- a/src/github/handlers/index.ts +++ b/src/github/handlers/index.ts @@ -33,10 +33,9 @@ async function shouldSkipPlugin(event: EmitterWebhookEvent, context: GitHubConte const manifest = await getManifest(context, pluginChain.uses[0].plugin); if ( context.key === "issue_comment.created" && - pluginChain.command && manifest && !Object.keys(manifest.commands).some( - (command) => "comment" in context.payload && typeof context.payload.comment !== "string" && context.payload.comment?.body.startsWith(command) + (command) => "comment" in context.payload && typeof context.payload.comment !== "string" && context.payload.comment?.body.startsWith(`/${command}`) ) ) { console.log(`Skipping plugin chain ${pluginChain.command} because command does not match`); diff --git a/src/github/types/plugin-configuration.ts b/src/github/types/plugin-configuration.ts index 1b569dc..cedecc2 100644 --- a/src/github/types/plugin-configuration.ts +++ b/src/github/types/plugin-configuration.ts @@ -69,9 +69,6 @@ export type PluginChain = StaticDecode; const handlerSchema = T.Array( T.Object({ name: T.Optional(T.String()), - description: T.Optional(T.String()), - command: T.Optional(T.String()), - example: T.Optional(T.String()), uses: pluginChainSchema, skipBotEvents: T.Boolean({ default: true }), }), diff --git a/src/github/utils/plugins.ts b/src/github/utils/plugins.ts index df86aa4..1ce8ff7 100644 --- a/src/github/utils/plugins.ts +++ b/src/github/utils/plugins.ts @@ -1,8 +1,9 @@ import { GithubPlugin, isGithubPlugin, PluginConfiguration } from "../types/plugin-configuration"; import { EmitterWebhookEventName } from "@octokit/webhooks"; import { GitHubContext } from "../github-context"; -import { Manifest } from "../../types/manifest"; +import { Manifest, manifestSchema, manifestValidator } from "../../types/manifest"; import { Buffer } from "node:buffer"; +import { Value } from "@sinclair/typebox/value"; const _manifestCache: Record = {}; @@ -29,7 +30,7 @@ async function fetchActionManifest(context: GitHubContext<"issue_comment.created }); if ("content" in data) { const content = Buffer.from(data.content, "base64").toString(); - const manifest = JSON.parse(content) as Manifest; + const manifest = decodeManifest(JSON.parse(content)); _manifestCache[manifestKey] = manifest; return manifest; } @@ -46,7 +47,7 @@ async function fetchWorkerManifest(url: string): Promise { const manifestUrl = `${url}/manifest.json`; try { const result = await fetch(manifestUrl); - const manifest = (await result.json()) as Manifest; + const manifest = decodeManifest(await result.json()); _manifestCache[url] = manifest; return manifest; } catch (e) { @@ -54,3 +55,15 @@ async function fetchWorkerManifest(url: string): Promise { } return null; } + +function decodeManifest(manifest: unknown) { + const defaultManifest = Value.Default(manifestSchema, manifest); + const errors = manifestValidator.testReturningErrors(manifest as Readonly); + if (errors !== null) { + for (const error of errors) { + console.error(error); + } + throw new Error("Manifest is invalid."); + } + return defaultManifest as Manifest; +} diff --git a/src/types/manifest.ts b/src/types/manifest.ts index d9c90df..0c01173 100644 --- a/src/types/manifest.ts +++ b/src/types/manifest.ts @@ -12,7 +12,7 @@ export const commandSchema = T.Object({ export const manifestSchema = T.Object({ name: T.String({ minLength: 1 }), description: T.String({ default: "" }), - commands: T.Record(T.String(), commandSchema), + commands: T.Record(T.String(), commandSchema, { default: {} }), "ubiquity:listeners": T.Optional(T.Array(runEvent, { default: [] })), }); diff --git a/tests/__mocks__/hello-world-plugin.ts b/tests/__mocks__/hello-world-plugin.ts index 3d59e2b..c98f512 100644 --- a/tests/__mocks__/hello-world-plugin.ts +++ b/tests/__mocks__/hello-world-plugin.ts @@ -1,6 +1,7 @@ import { createServer } from "@mswjs/http-middleware"; import { Octokit } from "@octokit/core"; import { http, HttpResponse } from "msw"; +import manifest from "./manifest.json"; type KernelInput = { authToken: string; @@ -43,6 +44,9 @@ const handlers = [ output: `{ "result": "success", "message": "${body.settings.response}" }`, }); }), + http.get("/manifest.json", () => { + return HttpResponse.json(manifest); + }), ]; const httpServer = createServer(...handlers); diff --git a/tests/__mocks__/manifest.json b/tests/__mocks__/manifest.json new file mode 100644 index 0000000..87b1ac0 --- /dev/null +++ b/tests/__mocks__/manifest.json @@ -0,0 +1,11 @@ +{ + "name": "Hello world", + "description": "Hello world plugin for testing.", + "ubiquity:listeners": [ "issue_comment.created" ], + "commands": { + "hello": { + "ubiquity:example": "/hello", + "description": "Greet Ubiquibot!" + } + } +}