Skip to content

Commit

Permalink
feat: manifest is now read and cached from the target repo
Browse files Browse the repository at this point in the history
  • Loading branch information
gentlementlegen committed Jul 12, 2024
1 parent af78d17 commit 76af3a0
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 54 deletions.
Binary file modified bun.lockb
Binary file not shown.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@
"dependencies": {
"@octokit/auth-app": "7.1.0",
"@octokit/core": "6.1.2",
"@octokit/plugin-paginate-rest": "11.3.0",
"@octokit/plugin-rest-endpoint-methods": "13.2.1",
"@octokit/plugin-paginate-rest": "11.3.3",
"@octokit/plugin-rest-endpoint-methods": "13.2.4",
"@octokit/plugin-retry": "7.1.1",
"@octokit/plugin-throttling": "9.3.0",
"@octokit/types": "13.5.0",
"@octokit/webhooks": "13.2.7",
"@octokit/webhooks": "13.2.8",
"@octokit/webhooks-types": "7.5.1",
"@sinclair/typebox": "0.32.33",
"dotenv": "16.4.5",
Expand Down
37 changes: 5 additions & 32 deletions src/github/handlers/help-command.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { getConfig } from "../utils/config";
import { GithubPlugin, isGithubPlugin } from "../types/plugin-configuration";
import { GithubPlugin } from "../types/plugin-configuration";
import { GitHubContext } from "../github-context";
import { Manifest, manifestSchema, manifestValidator } from "../../types/manifest";
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 (isGithubPlugin(plugin) ? fetchActionManifest(context, plugin) : fetchWorkerManifest(plugin));
const manifest = await getManifest(context, plugin);
if (manifest) {
Value.Default(manifestSchema, manifest);
const errors = manifestValidator.testReturningErrors(manifest);
Expand All @@ -18,7 +19,7 @@ async function parseCommandsFromManifest(context: GitHubContext<"issue_comment.c
} else {
if (manifest?.commands) {
for (const [key, value] of Object.entries(manifest.commands)) {
commands.push(`| \`/${getContent(key)}\` | ${getContent(value.description)} | \`${getContent(value["ubiquity:example"])}\` |`);
commands.push(`| \`/${getContent(key)}\` | ${getContent(value.description)} | \`${getContent(value["ubiquibot:example"])}\` |`);
}
}
}
Expand Down Expand Up @@ -55,31 +56,3 @@ export async function postHelpCommand(context: GitHubContext<"issue_comment.crea
function getContent(content: string | undefined) {
return content ? content.replace("|", "\\|") : "-";
}

async function fetchActionManifest(context: GitHubContext<"issue_comment.created">, { owner, repo }: GithubPlugin): Promise<Manifest | null> {
try {
const { data } = await context.octokit.repos.getContent({
owner,
repo,
path: "manifest.json",
});
if ("content" in data) {
const content = Buffer.from(data.content, "base64").toString();
return JSON.parse(content);
}
} catch (e) {
console.warn(`Could not find a manifest for ${owner}/${repo}: ${e}`);
}
return null;
}

async function fetchWorkerManifest(url: string): Promise<Manifest | null> {
const manifestUrl = `${url}/manifest.json`;
try {
const result = await fetch(manifestUrl);
return await result.json();
} catch (e) {
console.warn(`Could not find a manifest for ${manifestUrl}: ${e}`);
}
return null;
}
9 changes: 2 additions & 7 deletions src/github/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { repositoryDispatch } from "./repository-dispatch";
import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/workflow-dispatch";
import { PluginInput } from "../types/plugin";
import { isGithubPlugin, PluginConfiguration } from "../types/plugin-configuration";
import { getPluginsForEvent } from "../utils/plugins";

function tryCatchWrapper(fn: (event: EmitterWebhookEvent) => unknown) {
return async (event: EmitterWebhookEvent) => {
Expand Down Expand Up @@ -57,13 +58,7 @@ async function handleEvent(event: EmitterWebhookEvent, eventHandler: InstanceTyp
return;
}

const pluginChains = config.plugins.filter((plugin) => {
console.log("Plugin runs on", plugin.name, plugin.runsOn);
if (plugin.runsOn) {
return plugin.runsOn.includes(event.name);
}
return false;
});
const pluginChains = getPluginsForEvent(config.plugins, event.key);

if (pluginChains.length === 0) {
console.log(`No handler found for event ${event.name}`);
Expand Down
2 changes: 1 addition & 1 deletion src/github/types/plugin-configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const pluginChainSchema = T.Array(
id: T.Optional(T.String()),
plugin: githubPluginType(),
with: T.Record(T.String(), T.Unknown(), { default: {} }),
runsOn: T.Optional(T.Array(runEvent, { default: [] })),
}),
{ minItems: 1, default: [] }
);
Expand All @@ -65,7 +66,6 @@ const handlerSchema = T.Array(
example: T.Optional(T.String()),
uses: pluginChainSchema,
skipBotEvents: T.Boolean({ default: true }),
runsOn: T.Optional(T.Array(runEvent, { default: [] })),
}),
{ default: [] }
);
Expand Down
22 changes: 14 additions & 8 deletions src/github/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { GitHubContext } from "../github-context";
import { expressionRegex } from "../types/plugin";
import { configSchema, configSchemaValidator, PluginConfiguration } from "../types/plugin-configuration";
import { eventNames } from "../types/webhook-events";
import { getManifest, getPluginsForEvent } from "./plugins";

const UBIQUIBOT_CONFIG_FULL_PATH = ".github/.ubiquibot-config.yml";
const UBIQUIBOT_CONFIG_ORG_REPO = "ubiquibot-config";
Expand Down Expand Up @@ -39,11 +40,8 @@ async function getConfigurationFromRepo(context: GitHubContext, repository: stri
*/
function mergeConfigurations(configuration1: PluginConfiguration, configuration2: PluginConfiguration): PluginConfiguration {
const mergedConfiguration = { ...configuration1 };
for (const key of Object.keys(configuration2.plugins)) {
const pluginKey = key as keyof PluginConfiguration["plugins"];
if (configuration2.plugins[pluginKey]?.length) {
mergedConfiguration.plugins[pluginKey] = configuration2.plugins[pluginKey];
}
if (configuration2.plugins?.length) {
mergedConfiguration.plugins = configuration2.plugins;
}
return mergedConfiguration;
}
Expand Down Expand Up @@ -75,20 +73,28 @@ export async function getConfig(context: GitHubContext): Promise<PluginConfigura

checkPluginChains(mergedConfiguration);

for (const plugin of mergedConfiguration.plugins) {
if (plugin.uses.length && !plugin.uses[0].runsOn?.length) {
const manifest = await getManifest(context, plugin.uses[0].plugin);
if (manifest) {
plugin.uses[0].runsOn = manifest["ubiquibot:listeners"];
}
}
}
return mergedConfiguration;
}

function checkPluginChains(config: PluginConfiguration) {
for (const eventName of eventNames) {
const plugins = config.plugins[eventName];
const plugins = getPluginsForEvent(config.plugins, eventName);
for (const plugin of plugins) {
const allIds = checkPluginChainUniqueIds(plugin);
checkPluginChainExpressions(plugin, allIds);
}
}
}

function checkPluginChainUniqueIds(plugin: PluginConfiguration["plugins"]["*"][0]) {
function checkPluginChainUniqueIds(plugin: PluginConfiguration["plugins"][0]) {
const allIds = new Set<string>();
for (const use of plugin.uses) {
if (!use.id) continue;
Expand All @@ -101,7 +107,7 @@ function checkPluginChainUniqueIds(plugin: PluginConfiguration["plugins"]["*"][0
return allIds;
}

function checkPluginChainExpressions(plugin: PluginConfiguration["plugins"]["*"][0], allIds: Set<string>) {
function checkPluginChainExpressions(plugin: PluginConfiguration["plugins"][0], allIds: Set<string>) {
const calledIds = new Set<string>();
for (const use of plugin.uses) {
if (!use.id) continue;
Expand Down
55 changes: 55 additions & 0 deletions src/github/utils/plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { GithubPlugin, isGithubPlugin, PluginConfiguration } from "../types/plugin-configuration";
import { EmitterWebhookEventName } from "@octokit/webhooks";
import { GitHubContext } from "../github-context";
import { Manifest } from "../../types/manifest";

const _manifestCache: Record<string, Manifest> = {};

export function getPluginsForEvent(plugins: PluginConfiguration["plugins"], event: EmitterWebhookEventName) {
return plugins.filter((plugin) => {
return plugin.uses?.[0].runsOn?.includes(event);
});
}

export function getManifest(context: GitHubContext, plugin: string | GithubPlugin) {
return isGithubPlugin(plugin) ? fetchActionManifest(context, plugin) : fetchWorkerManifest(plugin);
}

async function fetchActionManifest(context: GitHubContext<"issue_comment.created">, { owner, repo }: GithubPlugin): Promise<Manifest | null> {
const manifestKey = `${owner}:${repo}`;
if (_manifestCache[manifestKey]) {
return _manifestCache[manifestKey];
}
try {
const { data } = await context.octokit.repos.getContent({
owner,
repo,
path: "manifest.json",
});
if ("content" in data) {
const content = Buffer.from(data.content, "base64").toString();
const manifest = JSON.parse(content) as Manifest;
_manifestCache[manifestKey] = manifest;
return manifest;
}
} catch (e) {
console.warn(`Could not find a manifest for ${owner}/${repo}: ${e}`);
}
return null;
}

async function fetchWorkerManifest(url: string): Promise<Manifest | null> {
if (_manifestCache[url]) {
return _manifestCache[url];
}
const manifestUrl = `${url}/manifest.json`;
try {
const result = await fetch(manifestUrl);
const manifest = (await result.json()) as Manifest;
_manifestCache[url] = manifest;
return manifest;
} catch (e) {
console.warn(`Could not find a manifest for ${manifestUrl}: ${e}`);
}
return null;
}
6 changes: 3 additions & 3 deletions src/types/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { type Static, Type as T } from "@sinclair/typebox";
import { StandardValidator } from "typebox-validators";
import { emitterEventNames } from "@octokit/webhooks/dist-types/generated/webhook-names";
import { emitterEventNames } from "@octokit/webhooks";

export const runEvent = T.Union(emitterEventNames.map((o) => T.Literal(o)));

export const commandSchema = T.Object({
description: T.String({ minLength: 1 }),
"ubiquity:example": T.String({ minLength: 1 }),
"ubiquibot:example": T.String({ minLength: 1 }),
});

export const manifestSchema = T.Object({
name: T.String({ minLength: 1 }),
description: T.String({ minLength: 1 }),
commands: T.Record(T.String(), commandSchema),
"ubiquity:runsOn": T.Optional(T.Array(runEvent, { default: [] })),
"ubiquibot:listeners": T.Optional(T.Array(runEvent, { default: [] })),
});

export const manifestValidator = new StandardValidator(manifestSchema);
Expand Down

0 comments on commit 76af3a0

Please sign in to comment.