From 2e111208ab80bedd4714942e7a7ef7ac95baf477 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Thu, 19 Sep 2024 19:16:40 +0900 Subject: [PATCH 01/34] chore(WIP): listen to configuration push --- bun.lockb | Bin 434290 -> 434290 bytes src/github/handlers/index.ts | 2 ++ src/github/handlers/push-event.ts | 14 ++++++++++++++ 3 files changed, 16 insertions(+) create mode 100644 src/github/handlers/push-event.ts diff --git a/bun.lockb b/bun.lockb index 0193fd3154c251785c36242dc151ddc9d49ee798..f592a85bc1207cd806dbd3ca72ee2f23191a5381 100755 GIT binary patch delta 11695 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 diff --git a/src/github/handlers/index.ts b/src/github/handlers/index.ts index 873a244..ce210c7 100644 --- a/src/github/handlers/index.ts +++ b/src/github/handlers/index.ts @@ -8,6 +8,7 @@ import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/wor import { PluginInput } from "../types/plugin"; import { isGithubPlugin, PluginConfiguration } from "../types/plugin-configuration"; import { getManifest, getPluginsForEvent } from "../utils/plugins"; +import handlePushEvent from "./push-event"; function tryCatchWrapper(fn: (event: EmitterWebhookEvent) => unknown) { return async (event: EmitterWebhookEvent) => { @@ -22,6 +23,7 @@ function tryCatchWrapper(fn: (event: EmitterWebhookEvent) => unknown) { export function bindHandlers(eventHandler: GitHubEventHandler) { eventHandler.on("repository_dispatch", repositoryDispatch); eventHandler.on("issue_comment.created", issueCommentCreated); + eventHandler.on("push", handlePushEvent); eventHandler.onAny(tryCatchWrapper((event) => handleEvent(event, eventHandler))); // onAny should also receive GithubContext but the types in octokit/webhooks are weird } diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts new file mode 100644 index 0000000..528d5b2 --- /dev/null +++ b/src/github/handlers/push-event.ts @@ -0,0 +1,14 @@ +import { GitHubContext } from "../github-context"; +import { CONFIG_FULL_PATH } from "../utils/config"; + +export default async function handlePushEvent(context: GitHubContext<"push">) { + console.log("context push", context); + const didFileChange = context.payload.commits.some((commit) => { + console.debug(commit); + return commit.modified?.includes(CONFIG_FULL_PATH) || commit.added?.includes(CONFIG_FULL_PATH) || commit.removed?.includes(CONFIG_FULL_PATH); + }); + + if (didFileChange) { + console.log("Configuration file changed, will run configuration checks."); + } +} From 2fe47a490d7d3b19d272836364f29752e3931613 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Thu, 19 Sep 2024 19:55:36 +0900 Subject: [PATCH 02/34] chore(WIP): comment validation on push --- src/github/handlers/push-event.ts | 31 ++++++++++++++++++++++++------- src/github/utils/config.ts | 2 +- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index 528d5b2..ae1b96a 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -1,14 +1,31 @@ import { GitHubContext } from "../github-context"; -import { CONFIG_FULL_PATH } from "../utils/config"; +import { CONFIG_FULL_PATH, getConfigurationFromRepo } from "../utils/config"; export default async function handlePushEvent(context: GitHubContext<"push">) { - console.log("context push", context); - const didFileChange = context.payload.commits.some((commit) => { - console.debug(commit); - return commit.modified?.includes(CONFIG_FULL_PATH) || commit.added?.includes(CONFIG_FULL_PATH) || commit.removed?.includes(CONFIG_FULL_PATH); - }); + const { octokit, payload } = context; + const { repository, commits, after } = payload; - if (didFileChange) { + const didConfigurationFileChange = commits.some( + (commit) => commit.modified?.includes(CONFIG_FULL_PATH) || commit.added?.includes(CONFIG_FULL_PATH) || commit.removed?.includes(CONFIG_FULL_PATH) + ); + + if (didConfigurationFileChange) { console.log("Configuration file changed, will run configuration checks."); + + if (repository.owner) { + const configuration = await getConfigurationFromRepo(context, repository.name, repository.owner.login); + if (configuration) { + try { + await octokit.rest.repos.createCommitComment({ + owner: repository.owner.login, + repo: repository.name, + commit_sha: after, + body: "Configuration is valid.", + }); + } catch (e) { + console.error(e); + } + } + } } } diff --git a/src/github/utils/config.ts b/src/github/utils/config.ts index ea26f33..e339988 100644 --- a/src/github/utils/config.ts +++ b/src/github/utils/config.ts @@ -8,7 +8,7 @@ import { getManifest } from "./plugins"; export const CONFIG_FULL_PATH = ".github/.ubiquibot-config.yml"; export const CONFIG_ORG_REPO = "ubiquibot-config"; -async function getConfigurationFromRepo(context: GitHubContext, repository: string, owner: string) { +export async function getConfigurationFromRepo(context: GitHubContext, repository: string, owner: string) { const targetRepoConfiguration: PluginConfiguration = parseYaml( await download({ context, From 53b0c4156e4ea9ef47376dc0cdcd4ac6f29e46fc Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Thu, 19 Sep 2024 21:57:09 +0900 Subject: [PATCH 03/34] chore(WIP): error details posting --- src/github/handlers/push-event.ts | 40 ++++++++++++++++++++++--------- src/github/utils/config.ts | 18 +++++++------- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index ae1b96a..fd27433 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -13,18 +13,36 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { console.log("Configuration file changed, will run configuration checks."); if (repository.owner) { - const configuration = await getConfigurationFromRepo(context, repository.name, repository.owner.login); - if (configuration) { - try { - await octokit.rest.repos.createCommitComment({ - owner: repository.owner.login, - repo: repository.name, - commit_sha: after, - body: "Configuration is valid.", - }); - } catch (e) { - console.error(e); + const { config, errors } = await getConfigurationFromRepo(context, repository.name, repository.owner.login); + if (config) { + // check each plugin + } + try { + const body = []; + + body.push(`@${payload.sender?.login} Configuration is ${!errors ? "valid" : "invalid"}.\n`); + console.log("errors detected", errors); + if (errors) { + for (const error of errors) { + if ("linePos" in error) { + body.push(`https://github.com/${repository.owner.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${4}`); + } + // See also https://eemeli.org/yaml/#content-nodes + body.push(`\n\`\`\`\n`); + body.push( + `${error.error ? JSON.stringify({ path: error.error.path, value: error.error.value, message: error.error.message }, null, 2) : error.message}` + ); + body.push(`\n\`\`\`\n`); + } } + await octokit.rest.repos.createCommitComment({ + owner: repository.owner.login, + repo: repository.name, + commit_sha: after, + body: body.join(""), + }); + } catch (e) { + console.error("handlePushEventError", e); } } } diff --git a/src/github/utils/config.ts b/src/github/utils/config.ts index e339988..437aeb9 100644 --- a/src/github/utils/config.ts +++ b/src/github/utils/config.ts @@ -9,13 +9,14 @@ export const CONFIG_FULL_PATH = ".github/.ubiquibot-config.yml"; export const CONFIG_ORG_REPO = "ubiquibot-config"; export async function getConfigurationFromRepo(context: GitHubContext, repository: string, owner: string) { - const targetRepoConfiguration: PluginConfiguration = parseYaml( + const { yaml, errors } = parseYaml( await download({ context, repository, owner, }) ); + const targetRepoConfiguration: PluginConfiguration | null = yaml; if (targetRepoConfiguration) { try { const configSchemaWithDefaults = Value.Default(configSchema, targetRepoConfiguration) as Readonly; @@ -25,13 +26,13 @@ export async function getConfigurationFromRepo(context: GitHubContext, repositor console.error(error); } } - return Value.Decode(configSchema, configSchemaWithDefaults); + return { config: Value.Decode(configSchema, configSchemaWithDefaults), errors }; } catch (error) { console.error(`Error decoding configuration for ${owner}/${repository}, will ignore.`, error); - return null; + return { config: null, errors: [error] }; } } - return null; + return { config: null, errors }; } /** @@ -65,8 +66,8 @@ export async function getConfig(context: GitHubContext): Promise { - if (configuration) { - mergedConfiguration = mergeConfigurations(mergedConfiguration, configuration); + if (configuration.config) { + mergedConfiguration = mergeConfigurations(mergedConfiguration, configuration.config); } }); @@ -157,10 +158,11 @@ export function parseYaml(data: null | string) { try { if (data) { const parsedData = YAML.parse(data); - return parsedData ?? null; + return { yaml: parsedData ?? null, errors: null }; } } catch (error) { console.error("Error parsing YAML", error); + return { errors: [error], yaml: null }; } - return null; + return { yaml: null, errors: null }; } From b479b203011470a3523eb8f03f3bd14d22888785 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Thu, 19 Sep 2024 23:08:17 +0900 Subject: [PATCH 04/34] chore(WIP): error details posting with line --- src/github/handlers/push-event.ts | 10 ++++++++-- src/github/utils/config.ts | 19 +++++++++---------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index fd27433..0c733df 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -1,5 +1,6 @@ import { GitHubContext } from "../github-context"; import { CONFIG_FULL_PATH, getConfigurationFromRepo } from "../utils/config"; +import YAML, { LineCounter } from "yaml"; export default async function handlePushEvent(context: GitHubContext<"push">) { const { octokit, payload } = context; @@ -13,7 +14,7 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { console.log("Configuration file changed, will run configuration checks."); if (repository.owner) { - const { config, errors } = await getConfigurationFromRepo(context, repository.name, repository.owner.login); + const { config, errors, rawData } = await getConfigurationFromRepo(context, repository.name, repository.owner.login); if (config) { // check each plugin } @@ -26,8 +27,13 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { for (const error of errors) { if ("linePos" in error) { body.push(`https://github.com/${repository.owner.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${4}`); + } else if (rawData) { + const lineCounter = new LineCounter(); + const doc = YAML.parseDocument(rawData, { lineCounter }); + const node = doc.getIn(["plugins", 0, "uses", 0, "plugin"], true); + const linePos = lineCounter.linePos(node.range[0]); + body.push(`https://github.com/${repository.owner.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${linePos.line}`); } - // See also https://eemeli.org/yaml/#content-nodes body.push(`\n\`\`\`\n`); body.push( `${error.error ? JSON.stringify({ path: error.error.path, value: error.error.value, message: error.error.message }, null, 2) : error.message}` diff --git a/src/github/utils/config.ts b/src/github/utils/config.ts index 437aeb9..b813e0d 100644 --- a/src/github/utils/config.ts +++ b/src/github/utils/config.ts @@ -9,13 +9,12 @@ export const CONFIG_FULL_PATH = ".github/.ubiquibot-config.yml"; export const CONFIG_ORG_REPO = "ubiquibot-config"; export async function getConfigurationFromRepo(context: GitHubContext, repository: string, owner: string) { - const { yaml, errors } = parseYaml( - await download({ - context, - repository, - owner, - }) - ); + const rawData = await download({ + context, + repository, + owner, + }); + const { yaml, errors } = parseYaml(rawData); const targetRepoConfiguration: PluginConfiguration | null = yaml; if (targetRepoConfiguration) { try { @@ -26,13 +25,13 @@ export async function getConfigurationFromRepo(context: GitHubContext, repositor console.error(error); } } - return { config: Value.Decode(configSchema, configSchemaWithDefaults), errors }; + return { config: Value.Decode(configSchema, configSchemaWithDefaults), errors, rawData }; } catch (error) { console.error(`Error decoding configuration for ${owner}/${repository}, will ignore.`, error); - return { config: null, errors: [error] }; + return { config: null, errors: [error], rawData }; } } - return { config: null, errors }; + return { config: null, errors, rawData }; } /** From b431101efd45bdb1dcd76bf98a540b0d6a6b4369 Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Fri, 20 Sep 2024 01:54:53 +0900 Subject: [PATCH 05/34] chore: removed additional properties --- src/github/handlers/push-event.ts | 18 ++++++++++-------- src/github/types/plugin-configuration.ts | 11 ++++++++--- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index 0c733df..2af7ac7 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -15,7 +15,7 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { if (repository.owner) { const { config, errors, rawData } = await getConfigurationFromRepo(context, repository.name, repository.owner.login); - if (config) { + if (!errors && config) { // check each plugin } try { @@ -26,18 +26,20 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { if (errors) { for (const error of errors) { if ("linePos" in error) { - body.push(`https://github.com/${repository.owner.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${4}`); + body.push(`https://github.com/${repository.owner.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${error.linePos[0].line}\n`); } else if (rawData) { const lineCounter = new LineCounter(); const doc = YAML.parseDocument(rawData, { lineCounter }); - const node = doc.getIn(["plugins", 0, "uses", 0, "plugin"], true); - const linePos = lineCounter.linePos(node.range[0]); - body.push(`https://github.com/${repository.owner.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${linePos.line}`); + const path = error.error.path + .split("/") + .filter((o) => o) + .slice(0, -1); + const node = doc.getIn(path, true); + const linePosStart = lineCounter.linePos(node.range[0]); + body.push(`https://github.com/${repository.owner.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${linePosStart.line}`); } body.push(`\n\`\`\`\n`); - body.push( - `${error.error ? JSON.stringify({ path: error.error.path, value: error.error.value, message: error.error.message }, null, 2) : error.message}` - ); + body.push(`${error.error ? error.error.message : error.message}`); body.push(`\n\`\`\`\n`); } } diff --git a/src/github/types/plugin-configuration.ts b/src/github/types/plugin-configuration.ts index 1ead40c..02316d7 100644 --- a/src/github/types/plugin-configuration.ts +++ b/src/github/types/plugin-configuration.ts @@ -75,9 +75,14 @@ const handlerSchema = T.Array( { default: [] } ); -export const configSchema = T.Object({ - plugins: handlerSchema, -}); +export const configSchema = T.Object( + { + plugins: handlerSchema, + }, + { + additionalProperties: false, + } +); export const configSchemaValidator = new StandardValidator(configSchema); From 8f14418266272c7b4e79524fb3beb4219a808f6a Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Fri, 20 Sep 2024 14:03:21 +0900 Subject: [PATCH 06/34] chore: fixed error types --- src/github/handlers/push-event.ts | 31 +++++++++++++++++++------------ src/github/utils/config.ts | 8 ++++---- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index 2af7ac7..f549f54 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -1,6 +1,6 @@ import { GitHubContext } from "../github-context"; import { CONFIG_FULL_PATH, getConfigurationFromRepo } from "../utils/config"; -import YAML, { LineCounter } from "yaml"; +import YAML, { LineCounter, Node, YAMLError } from "yaml"; export default async function handlePushEvent(context: GitHubContext<"push">) { const { octokit, payload } = context; @@ -20,27 +20,34 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { } try { const body = []; - body.push(`@${payload.sender?.login} Configuration is ${!errors ? "valid" : "invalid"}.\n`); - console.log("errors detected", errors); if (errors) { for (const error of errors) { - if ("linePos" in error) { - body.push(`https://github.com/${repository.owner.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${error.linePos[0].line}\n`); + body.push("> [!CAUTION]\n"); + if (error instanceof YAMLError) { + body.push(`> https://github.com/${repository.owner.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${error.linePos?.[0].line}`); } else if (rawData) { const lineCounter = new LineCounter(); const doc = YAML.parseDocument(rawData, { lineCounter }); - const path = error.error.path + const path = error.path .split("/") .filter((o) => o) .slice(0, -1); - const node = doc.getIn(path, true); - const linePosStart = lineCounter.linePos(node.range[0]); - body.push(`https://github.com/${repository.owner.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${linePosStart.line}`); + const node = doc.getIn(path, true) as Node; + const linePosStart = lineCounter.linePos(node.range?.[0] || 0); + body.push(`> https://github.com/${repository.owner.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${linePosStart.line}`); + } + const message = []; + if (error instanceof YAMLError) { + message.push(error.message); + } else { + message.push(`path: ${error.path}\n`); + message.push(`value: ${error.value}\n`); + message.push(`message: ${error.message}`); } - body.push(`\n\`\`\`\n`); - body.push(`${error.error ? error.error.message : error.message}`); - body.push(`\n\`\`\`\n`); + body.push(`\n> \`\`\`\n`); + body.push(`> ${message.join("").replaceAll("\n", "\n> ")}`); + body.push(`\n> \`\`\`\n`); } } await octokit.rest.repos.createCommitComment({ diff --git a/src/github/utils/config.ts b/src/github/utils/config.ts index b813e0d..554ecd2 100644 --- a/src/github/utils/config.ts +++ b/src/github/utils/config.ts @@ -1,5 +1,5 @@ -import { Value } from "@sinclair/typebox/value"; -import YAML from "yaml"; +import { TransformDecodeCheckError, Value, ValueError } from "@sinclair/typebox/value"; +import YAML, { YAMLError } from "yaml"; import { GitHubContext } from "../github-context"; import { expressionRegex } from "../types/plugin"; import { configSchema, configSchemaValidator, PluginConfiguration } from "../types/plugin-configuration"; @@ -28,7 +28,7 @@ export async function getConfigurationFromRepo(context: GitHubContext, repositor return { config: Value.Decode(configSchema, configSchemaWithDefaults), errors, rawData }; } catch (error) { console.error(`Error decoding configuration for ${owner}/${repository}, will ignore.`, error); - return { config: null, errors: [error], rawData }; + return { config: null, errors: [error instanceof TransformDecodeCheckError ? error.error : error] as ValueError[], rawData }; } } return { config: null, errors, rawData }; @@ -161,7 +161,7 @@ export function parseYaml(data: null | string) { } } catch (error) { console.error("Error parsing YAML", error); - return { errors: [error], yaml: null }; + return { errors: [error] as YAMLError[], yaml: null }; } return { yaml: null, errors: null }; } From 3f67859b3f7f1b37ea323680306fa6290a26cb79 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Fri, 20 Sep 2024 15:31:21 +0900 Subject: [PATCH 07/34] chore: split error body construction --- src/github/handlers/push-event.ts | 68 +++++++++++++++++++------------ 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index f549f54..f3c4cf1 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -1,6 +1,46 @@ import { GitHubContext } from "../github-context"; import { CONFIG_FULL_PATH, getConfigurationFromRepo } from "../utils/config"; import YAML, { LineCounter, Node, YAMLError } from "yaml"; +import { ValueError } from "typebox-validators"; + +function constructErrorBody( + errors: Iterable | ValueError[] | YAML.YAMLError[], + rawData: string | null, + repository: GitHubContext<"push">["payload"]["repository"], + after: string +) { + const body = []; + if (errors) { + for (const error of errors) { + body.push("> [!CAUTION]\n"); + if (error instanceof YAMLError) { + body.push(`> https://github.com/${repository.owner?.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${error.linePos?.[0].line}`); + } else if (rawData) { + const lineCounter = new LineCounter(); + const doc = YAML.parseDocument(rawData, { lineCounter }); + const path = error.path + .split("/") + .filter((o) => o) + .slice(0, -1); + const node = doc.getIn(path, true) as Node; + const linePosStart = lineCounter.linePos(node.range?.[0] || 0); + body.push(`> https://github.com/${repository.owner?.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${linePosStart.line}`); + } + const message = []; + if (error instanceof YAMLError) { + message.push(error.message); + } else { + message.push(`path: ${error.path}\n`); + message.push(`value: ${error.value}\n`); + message.push(`message: ${error.message}`); + } + body.push(`\n> \`\`\`\n`); + body.push(`> ${message.join("").replaceAll("\n", "\n> ")}`); + body.push(`\n> \`\`\`\n`); + } + } + return body; +} export default async function handlePushEvent(context: GitHubContext<"push">) { const { octokit, payload } = context; @@ -22,33 +62,7 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { const body = []; body.push(`@${payload.sender?.login} Configuration is ${!errors ? "valid" : "invalid"}.\n`); if (errors) { - for (const error of errors) { - body.push("> [!CAUTION]\n"); - if (error instanceof YAMLError) { - body.push(`> https://github.com/${repository.owner.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${error.linePos?.[0].line}`); - } else if (rawData) { - const lineCounter = new LineCounter(); - const doc = YAML.parseDocument(rawData, { lineCounter }); - const path = error.path - .split("/") - .filter((o) => o) - .slice(0, -1); - const node = doc.getIn(path, true) as Node; - const linePosStart = lineCounter.linePos(node.range?.[0] || 0); - body.push(`> https://github.com/${repository.owner.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${linePosStart.line}`); - } - const message = []; - if (error instanceof YAMLError) { - message.push(error.message); - } else { - message.push(`path: ${error.path}\n`); - message.push(`value: ${error.value}\n`); - message.push(`message: ${error.message}`); - } - body.push(`\n> \`\`\`\n`); - body.push(`> ${message.join("").replaceAll("\n", "\n> ")}`); - body.push(`\n> \`\`\`\n`); - } + body.push(...constructErrorBody(errors, rawData, repository, after)); } await octokit.rest.repos.createCommitComment({ owner: repository.owner.login, From 16d94b8abaa54b26369ad79cb417402029d33f0e Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Fri, 20 Sep 2024 17:38:23 +0900 Subject: [PATCH 08/34] chore(WIP): plugin remote check --- src/github/handlers/push-event.ts | 32 ++++++++++++++++++++++--------- src/github/utils/plugins.ts | 2 +- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index f3c4cf1..5ef6705 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -2,6 +2,7 @@ import { GitHubContext } from "../github-context"; import { CONFIG_FULL_PATH, getConfigurationFromRepo } from "../utils/config"; import YAML, { LineCounter, Node, YAMLError } from "yaml"; import { ValueError } from "typebox-validators"; +import { dispatchWorker } from "../utils/workflow-dispatch"; function constructErrorBody( errors: Iterable | ValueError[] | YAML.YAMLError[], @@ -18,10 +19,9 @@ function constructErrorBody( } else if (rawData) { const lineCounter = new LineCounter(); const doc = YAML.parseDocument(rawData, { lineCounter }); - const path = error.path - .split("/") - .filter((o) => o) - .slice(0, -1); + const path = error.path.split("/").filter((o) => o); + // .slice(0, -1); TODO: depending if missing, slice or not + console.log("+++ path", path); const node = doc.getIn(path, true) as Node; const linePosStart = lineCounter.linePos(node.range?.[0] || 0); body.push(`> https://github.com/${repository.owner?.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${linePosStart.line}`); @@ -36,7 +36,7 @@ function constructErrorBody( } body.push(`\n> \`\`\`\n`); body.push(`> ${message.join("").replaceAll("\n", "\n> ")}`); - body.push(`\n> \`\`\`\n`); + body.push(`\n> \`\`\`\n\n`); } } return body; @@ -54,14 +54,28 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { console.log("Configuration file changed, will run configuration checks."); if (repository.owner) { - const { config, errors, rawData } = await getConfigurationFromRepo(context, repository.name, repository.owner.login); - if (!errors && config) { + const { config, errors: configurationErrors, rawData } = await getConfigurationFromRepo(context, repository.name, repository.owner.login); + const errors = []; + if (!configurationErrors && config) { // check each plugin + for (const { uses } of config.plugins) { + for (const use of uses) { + if (typeof use.plugin === "string") { + const response = await dispatchWorker(`${use.plugin}/manifest`, { settings: use.with }); + console.log(response); + if (response.errors) { + errors.push(...response.errors.map((err) => ({ ...err, path: `plugins/0/uses/0/with${err.path}` }))); + } + } + } + } + } else if (configurationErrors) { + errors.push(...configurationErrors); } try { const body = []; - body.push(`@${payload.sender?.login} Configuration is ${!errors ? "valid" : "invalid"}.\n`); - if (errors) { + body.push(`@${payload.sender?.login} Configuration is ${!errors.length ? "valid" : "invalid"}.\n`); + if (errors.length) { body.push(...constructErrorBody(errors, rawData, repository, after)); } await octokit.rest.repos.createCommitComment({ diff --git a/src/github/utils/plugins.ts b/src/github/utils/plugins.ts index a084694..2c8a034 100644 --- a/src/github/utils/plugins.ts +++ b/src/github/utils/plugins.ts @@ -45,7 +45,7 @@ async function fetchWorkerManifest(url: string): Promise { if (_manifestCache[url]) { return _manifestCache[url]; } - const manifestUrl = `${url}/manifest.json`; + const manifestUrl = `${url}/manifest`; try { const result = await fetch(manifestUrl); const manifest = decodeManifest(await result.json()); From 65a1507434a41ae668be0d755ecd483a69faca21 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Fri, 20 Sep 2024 23:41:36 +0900 Subject: [PATCH 09/34] chore(WIP): plugin remote check --- src/github/handlers/push-event.ts | 62 +++++++++++++++++++--- src/github/handlers/repository-dispatch.ts | 5 +- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index 5ef6705..70da90b 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -2,7 +2,7 @@ import { GitHubContext } from "../github-context"; import { CONFIG_FULL_PATH, getConfigurationFromRepo } from "../utils/config"; import YAML, { LineCounter, Node, YAMLError } from "yaml"; import { ValueError } from "typebox-validators"; -import { dispatchWorker } from "../utils/workflow-dispatch"; +import { dispatchWorker, dispatchWorkflow } from "../utils/workflow-dispatch"; function constructErrorBody( errors: Iterable | ValueError[] | YAML.YAMLError[], @@ -42,6 +42,41 @@ function constructErrorBody( return body; } +// TODO: store id within KV and get payload from there +export async function handleActionValidationWorkflowCompleted(context: GitHubContext<"repository_dispatch">) { + const { octokit, payload } = context; + const { repository, client_payload } = payload; + + if (client_payload && "output" in client_payload) { + const { rawData } = await getConfigurationFromRepo(context, repository.name, repository.owner.login); + const result = JSON.parse(client_payload.output); + console.log("Received Action output result for validation, will process."); + console.log(result); + const errors = result.errors; + try { + const body = []; + body.push(`@${payload.sender?.login} Configuration is ${!errors.length ? "valid" : "invalid"}.\n`); + if (errors.length) { + body.push(...constructErrorBody(errors, rawData, repository, result.after)); + } + console.log("+))) creating commit comment", { + owner: repository.owner.login, + repo: repository.name, + commit_sha: result.after, + body: body.join(""), + }); + await octokit.rest.repos.createCommitComment({ + owner: repository.owner.login, + repo: repository.name, + commit_sha: result.after, + body: body.join(""), + }); + } catch (e) { + console.error("handleActionValidationWorkflowCompleted", e); + } + } +} + export default async function handlePushEvent(context: GitHubContext<"push">) { const { octokit, payload } = context; const { repository, commits, after } = payload; @@ -56,16 +91,25 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { if (repository.owner) { const { config, errors: configurationErrors, rawData } = await getConfigurationFromRepo(context, repository.name, repository.owner.login); const errors = []; + // TODO test unreachable endpoints if (!configurationErrors && config) { - // check each plugin - for (const { uses } of config.plugins) { - for (const use of uses) { + for (let i = 0; i < config.plugins.length; ++i) { + const { uses } = config.plugins[i]; + for (let j = 0; j < uses.length; ++j) { + const use = uses[j]; if (typeof use.plugin === "string") { const response = await dispatchWorker(`${use.plugin}/manifest`, { settings: use.with }); - console.log(response); if (response.errors) { - errors.push(...response.errors.map((err) => ({ ...err, path: `plugins/0/uses/0/with${err.path}` }))); + errors.push(...response.errors.map((err) => ({ ...err, path: `plugins/${i}/uses/${j}/with${err.path}` }))); } + } else { + await dispatchWorkflow(context, { + owner: use.plugin.owner, + ref: use.plugin.ref, + repository: use.plugin.repo, + workflowId: "validate-schema.yml", + inputs: { settings: JSON.stringify(use.with), after }, + }); } } } @@ -78,6 +122,12 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { if (errors.length) { body.push(...constructErrorBody(errors, rawData, repository, after)); } + console.log("))) creating commit comment", { + owner: repository.owner.login, + repo: repository.name, + commit_sha: after, + body: body.join(""), + }); await octokit.rest.repos.createCommitComment({ owner: repository.owner.login, repo: repository.name, diff --git a/src/github/handlers/repository-dispatch.ts b/src/github/handlers/repository-dispatch.ts index b421b8e..ae201f1 100644 --- a/src/github/handlers/repository-dispatch.ts +++ b/src/github/handlers/repository-dispatch.ts @@ -3,11 +3,14 @@ import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/wor import { Value } from "@sinclair/typebox/value"; import { PluginInput, PluginChainState, expressionRegex, pluginOutputSchema } from "../types/plugin"; import { isGithubPlugin } from "../types/plugin-configuration"; +import { handleActionValidationWorkflowCompleted } from "./push-event"; export async function repositoryDispatch(context: GitHubContext<"repository_dispatch">) { console.log("Repository dispatch event received", context.payload.client_payload); - if (context.payload.action !== "return_data_to_ubiquibot_kernel") { + if (context.payload.action === "configuration_validation") { + return handleActionValidationWorkflowCompleted(context); + } else if (context.payload.action !== "return_data_to_ubiquibot_kernel") { console.log("Skipping non-ubiquibot event"); return; } From de80953852a459d84f34ff6ef9d79b8b57dc3a71 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Sat, 21 Sep 2024 10:49:09 +0900 Subject: [PATCH 10/34] feat!: payload is fetched from KV --- src/github/handlers/push-event.ts | 111 +++++++++++++++++++++--------- src/github/types/plugin.ts | 1 + 2 files changed, 79 insertions(+), 33 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index 70da90b..a8843bd 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -2,7 +2,10 @@ import { GitHubContext } from "../github-context"; import { CONFIG_FULL_PATH, getConfigurationFromRepo } from "../utils/config"; import YAML, { LineCounter, Node, YAMLError } from "yaml"; import { ValueError } from "typebox-validators"; -import { dispatchWorker, dispatchWorkflow } from "../utils/workflow-dispatch"; +import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/workflow-dispatch"; +import { PluginInput, PluginOutput, pluginOutputSchema } from "../types/plugin"; +import { isGithubPlugin } from "../types/plugin-configuration"; +import { Value } from "@sinclair/typebox/value"; function constructErrorBody( errors: Iterable | ValueError[] | YAML.YAMLError[], @@ -42,43 +45,64 @@ function constructErrorBody( return body; } -// TODO: store id within KV and get payload from there export async function handleActionValidationWorkflowCompleted(context: GitHubContext<"repository_dispatch">) { const { octokit, payload } = context; const { repository, client_payload } = payload; + let pluginOutput: PluginOutput; - if (client_payload && "output" in client_payload) { - const { rawData } = await getConfigurationFromRepo(context, repository.name, repository.owner.login); - const result = JSON.parse(client_payload.output); - console.log("Received Action output result for validation, will process."); - console.log(result); - const errors = result.errors; - try { - const body = []; - body.push(`@${payload.sender?.login} Configuration is ${!errors.length ? "valid" : "invalid"}.\n`); - if (errors.length) { - body.push(...constructErrorBody(errors, rawData, repository, result.after)); - } - console.log("+))) creating commit comment", { - owner: repository.owner.login, - repo: repository.name, - commit_sha: result.after, - body: body.join(""), - }); + try { + pluginOutput = Value.Decode(pluginOutputSchema, client_payload); + } catch (error) { + console.error("Cannot decode plugin output", error); + throw error; + } + + const state = await context.eventHandler.pluginChainState.get(pluginOutput.state_id); + + if (!state) { + console.error(`[handleActionValidationWorkflowCompleted]: No state found for plugin chain ${pluginOutput.state_id}`); + return; + } + + console.log("Received Action output result for validation, will process.", pluginOutput.output); + const errors = pluginOutput.output.errors as ValueError[]; + console.log("=== stuff", JSON.stringify(payload, null, 2), JSON.stringify(state, null, 2)); + // TODO: validate with typebox + const { rawData, after, configurationRepo, path } = state.additionalProperties ?? {}; + try { + const body = []; + body.push(`@${state.eventPayload.sender?.login} Configuration is ${!errors.length ? "valid" : "invalid"}.\n`); + if (errors.length) { + body.push( + ...constructErrorBody( + errors.map((err) => ({ ...err, path: `${path}${err.path}` })), + rawData as string, + configurationRepo as GitHubContext<"push">["payload"]["repository"], + after as string + ) + ); + } + console.log("+))) creating commit comment", { + owner: repository.owner.login, + repo: repository.name, + commit_sha: state.additionalProperties?.after, + body: body.join(""), + }); + if (after) { await octokit.rest.repos.createCommitComment({ - owner: repository.owner.login, - repo: repository.name, - commit_sha: result.after, + owner: configurationRepo.owner.login, + repo: configurationRepo.name, + commit_sha: after as string, body: body.join(""), }); - } catch (e) { - console.error("handleActionValidationWorkflowCompleted", e); } + } catch (e) { + console.error("handleActionValidationWorkflowCompleted", e); } } export default async function handlePushEvent(context: GitHubContext<"push">) { - const { octokit, payload } = context; + const { octokit, payload, eventHandler } = context; const { repository, commits, after } = payload; const didConfigurationFileChange = commits.some( @@ -96,19 +120,40 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { for (let i = 0; i < config.plugins.length; ++i) { const { uses } = config.plugins[i]; for (let j = 0; j < uses.length; ++j) { - const use = uses[j]; - if (typeof use.plugin === "string") { - const response = await dispatchWorker(`${use.plugin}/manifest`, { settings: use.with }); + const { plugin, with: args } = uses[j]; + const isGithubPluginObject = isGithubPlugin(plugin); + const stateId = crypto.randomUUID(); + const token = payload.installation ? await eventHandler.getToken(payload.installation.id) : ""; + const ref = isGithubPluginObject ? (plugin.ref ?? (await getDefaultBranch(context, plugin.owner, plugin.repo))) : plugin; + const inputs = new PluginInput(context.eventHandler, stateId, context.key, payload, args, token, ref); + + if (!isGithubPluginObject) { + const response = await dispatchWorker(`${plugin}/manifest`, await inputs.getWorkerInputs()); if (response.errors) { errors.push(...response.errors.map((err) => ({ ...err, path: `plugins/${i}/uses/${j}/with${err.path}` }))); } } else { + await eventHandler.pluginChainState.put(stateId, { + eventPayload: payload, + currentPlugin: 0, + eventId: "", + eventName: "push", + inputs: [inputs], + outputs: new Array(uses.length), + pluginChain: uses, + additionalProperties: { + after, + configurationRepo: repository, + rawData, + path: `plugins/${i}/uses/${j}/with`, + }, + }); await dispatchWorkflow(context, { - owner: use.plugin.owner, - ref: use.plugin.ref, - repository: use.plugin.repo, + owner: plugin.owner, + repository: plugin.repo, workflowId: "validate-schema.yml", - inputs: { settings: JSON.stringify(use.with), after }, + ref: plugin.ref, + inputs: inputs.getWorkflowInputs(), }); } } diff --git a/src/github/types/plugin.ts b/src/github/types/plugin.ts index d429678..79b4cec 100644 --- a/src/github/types/plugin.ts +++ b/src/github/types/plugin.ts @@ -81,4 +81,5 @@ export type PluginChainState; }; From 65d5b39ad72a83a7f9c8b8f0354d7d5b90651da6 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Sat, 21 Sep 2024 10:59:07 +0900 Subject: [PATCH 11/34] chore: removed logs --- src/github/handlers/push-event.ts | 71 +++++++++++++------------------ 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index a8843bd..ea0d3be 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -47,13 +47,13 @@ function constructErrorBody( export async function handleActionValidationWorkflowCompleted(context: GitHubContext<"repository_dispatch">) { const { octokit, payload } = context; - const { repository, client_payload } = payload; + const { client_payload } = payload; let pluginOutput: PluginOutput; try { pluginOutput = Value.Decode(pluginOutputSchema, client_payload); } catch (error) { - console.error("Cannot decode plugin output", error); + console.error("[handleActionValidationWorkflowCompleted]: Cannot decode plugin output", error); throw error; } @@ -66,35 +66,30 @@ export async function handleActionValidationWorkflowCompleted(context: GitHubCon console.log("Received Action output result for validation, will process.", pluginOutput.output); const errors = pluginOutput.output.errors as ValueError[]; - console.log("=== stuff", JSON.stringify(payload, null, 2), JSON.stringify(state, null, 2)); // TODO: validate with typebox const { rawData, after, configurationRepo, path } = state.additionalProperties ?? {}; try { - const body = []; - body.push(`@${state.eventPayload.sender?.login} Configuration is ${!errors.length ? "valid" : "invalid"}.\n`); if (errors.length) { - body.push( - ...constructErrorBody( - errors.map((err) => ({ ...err, path: `${path}${err.path}` })), - rawData as string, - configurationRepo as GitHubContext<"push">["payload"]["repository"], - after as string - ) - ); - } - console.log("+))) creating commit comment", { - owner: repository.owner.login, - repo: repository.name, - commit_sha: state.additionalProperties?.after, - body: body.join(""), - }); - if (after) { - await octokit.rest.repos.createCommitComment({ - owner: configurationRepo.owner.login, - repo: configurationRepo.name, - commit_sha: after as string, - body: body.join(""), - }); + const body = []; + body.push(`@${state.eventPayload.sender?.login} Configuration is invalid.\n`); + if (errors.length) { + body.push( + ...constructErrorBody( + errors.map((err) => ({ ...err, path: `${path}${err.path}` })), + rawData as string, + configurationRepo as GitHubContext<"push">["payload"]["repository"], + after as string + ) + ); + } + if (after) { + await octokit.rest.repos.createCommitComment({ + owner: configurationRepo.owner.login, + repo: configurationRepo.name, + commit_sha: after as string, + body: body.join(""), + }); + } } } catch (e) { console.error("handleActionValidationWorkflowCompleted", e); @@ -162,23 +157,17 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { errors.push(...configurationErrors); } try { - const body = []; - body.push(`@${payload.sender?.login} Configuration is ${!errors.length ? "valid" : "invalid"}.\n`); if (errors.length) { + const body = []; + body.push(`@${payload.sender?.login} Configuration is invalid.\n`); body.push(...constructErrorBody(errors, rawData, repository, after)); + await octokit.rest.repos.createCommitComment({ + owner: repository.owner.login, + repo: repository.name, + commit_sha: after, + body: body.join(""), + }); } - console.log("))) creating commit comment", { - owner: repository.owner.login, - repo: repository.name, - commit_sha: after, - body: body.join(""), - }); - await octokit.rest.repos.createCommitComment({ - owner: repository.owner.login, - repo: repository.name, - commit_sha: after, - body: body.join(""), - }); } catch (e) { console.error("handlePushEventError", e); } From f122def4c7d6217d27cb4d293e2601886aa17501 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Sat, 21 Sep 2024 12:09:13 +0900 Subject: [PATCH 12/34] chore: typebox validation for payload --- src/github/handlers/push-event.ts | 46 ++++++++++++-------- src/github/types/state-validation-payload.ts | 17 ++++++++ 2 files changed, 44 insertions(+), 19 deletions(-) create mode 100644 src/github/types/state-validation-payload.ts diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index ea0d3be..b75dbdd 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -6,6 +6,7 @@ import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/wor import { PluginInput, PluginOutput, pluginOutputSchema } from "../types/plugin"; import { isGithubPlugin } from "../types/plugin-configuration"; import { Value } from "@sinclair/typebox/value"; +import { StateValidation, stateValidationSchema } from "../types/state-validation-payload"; function constructErrorBody( errors: Iterable | ValueError[] | YAML.YAMLError[], @@ -18,7 +19,7 @@ function constructErrorBody( for (const error of errors) { body.push("> [!CAUTION]\n"); if (error instanceof YAMLError) { - body.push(`> https://github.com/${repository.owner?.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${error.linePos?.[0].line}`); + body.push(`> https://github.com/${repository.owner?.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${error.linePos?.[0].line || 0}`); } else if (rawData) { const lineCounter = new LineCounter(); const doc = YAML.parseDocument(rawData, { lineCounter }); @@ -26,7 +27,7 @@ function constructErrorBody( // .slice(0, -1); TODO: depending if missing, slice or not console.log("+++ path", path); const node = doc.getIn(path, true) as Node; - const linePosStart = lineCounter.linePos(node.range?.[0] || 0); + const linePosStart = lineCounter.linePos(node?.range?.[0] || 0); body.push(`> https://github.com/${repository.owner?.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${linePosStart.line}`); } const message = []; @@ -49,6 +50,7 @@ export async function handleActionValidationWorkflowCompleted(context: GitHubCon const { octokit, payload } = context; const { client_payload } = payload; let pluginOutput: PluginOutput; + let stateValidation: StateValidation; try { pluginOutput = Value.Decode(pluginOutputSchema, client_payload); @@ -65,31 +67,41 @@ export async function handleActionValidationWorkflowCompleted(context: GitHubCon } console.log("Received Action output result for validation, will process.", pluginOutput.output); + const errors = pluginOutput.output.errors as ValueError[]; - // TODO: validate with typebox - const { rawData, after, configurationRepo, path } = state.additionalProperties ?? {}; + try { + stateValidation = Value.Decode(stateValidationSchema, state.additionalProperties); + } catch (e) { + console.error(`[handleActionValidationWorkflowCompleted]: Cannot decode state properties`); + throw e; + } + if (!stateValidation) { + console.error(`[handleActionValidationWorkflowCompleted]: State validation is invalid for ${pluginOutput.state_id}`); + return; + } + + const { rawData, path } = state.additionalProperties ?? {}; try { if (errors.length) { const body = []; + console.log("+++", JSON.stringify(state, null, 2)); body.push(`@${state.eventPayload.sender?.login} Configuration is invalid.\n`); if (errors.length) { body.push( ...constructErrorBody( errors.map((err) => ({ ...err, path: `${path}${err.path}` })), rawData as string, - configurationRepo as GitHubContext<"push">["payload"]["repository"], - after as string + state.eventPayload.repository as GitHubContext<"push">["payload"]["repository"], + state.eventPayload.after as string ) ); } - if (after) { - await octokit.rest.repos.createCommitComment({ - owner: configurationRepo.owner.login, - repo: configurationRepo.name, - commit_sha: after as string, - body: body.join(""), - }); - } + await octokit.rest.repos.createCommitComment({ + owner: state.eventPayload.repository.owner.login, + repo: state.eventPayload.repository.name, + commit_sha: state.eventPayload.after as string, + body: body.join(""), + }); } } catch (e) { console.error("handleActionValidationWorkflowCompleted", e); @@ -100,9 +112,7 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { const { octokit, payload, eventHandler } = context; const { repository, commits, after } = payload; - const didConfigurationFileChange = commits.some( - (commit) => commit.modified?.includes(CONFIG_FULL_PATH) || commit.added?.includes(CONFIG_FULL_PATH) || commit.removed?.includes(CONFIG_FULL_PATH) - ); + const didConfigurationFileChange = commits.some((commit) => commit.modified?.includes(CONFIG_FULL_PATH) || commit.added?.includes(CONFIG_FULL_PATH)); if (didConfigurationFileChange) { console.log("Configuration file changed, will run configuration checks."); @@ -137,8 +147,6 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { outputs: new Array(uses.length), pluginChain: uses, additionalProperties: { - after, - configurationRepo: repository, rawData, path: `plugins/${i}/uses/${j}/with`, }, diff --git a/src/github/types/state-validation-payload.ts b/src/github/types/state-validation-payload.ts new file mode 100644 index 0000000..a54ef49 --- /dev/null +++ b/src/github/types/state-validation-payload.ts @@ -0,0 +1,17 @@ +import { StaticDecode, Type } from "@sinclair/typebox"; +import { StandardValidator } from "typebox-validators"; + +export const stateValidationSchema = Type.Object({ + /** + * The YAML raw data + */ + rawData: Type.String(), + /** + * Path to the YAML element in the document + */ + path: Type.String(), +}); + +export const stateValidationValidator = new StandardValidator(stateValidationSchema); + +export type StateValidation = StaticDecode; From e95b6a850f380372ac5a416d3fcb6644e45febe0 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Sat, 21 Sep 2024 12:17:09 +0900 Subject: [PATCH 13/34] chore: split path on missing property --- src/github/handlers/push-event.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index b75dbdd..0dde77e 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -5,7 +5,7 @@ import { ValueError } from "typebox-validators"; import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/workflow-dispatch"; import { PluginInput, PluginOutput, pluginOutputSchema } from "../types/plugin"; import { isGithubPlugin } from "../types/plugin-configuration"; -import { Value } from "@sinclair/typebox/value"; +import { Value, ValueErrorType } from "@sinclair/typebox/value"; import { StateValidation, stateValidationSchema } from "../types/state-validation-payload"; function constructErrorBody( @@ -24,8 +24,9 @@ function constructErrorBody( const lineCounter = new LineCounter(); const doc = YAML.parseDocument(rawData, { lineCounter }); const path = error.path.split("/").filter((o) => o); - // .slice(0, -1); TODO: depending if missing, slice or not - console.log("+++ path", path); + if (error.type === ValueErrorType.ObjectRequiredProperty) { + path.splice(path.length - 1, 1); + } const node = doc.getIn(path, true) as Node; const linePosStart = lineCounter.linePos(node?.range?.[0] || 0); body.push(`> https://github.com/${repository.owner?.login}/${repository.name}/blob/${after}/${CONFIG_FULL_PATH}#L${linePosStart.line}`); From a7c512161772bc9ab90a759746e5d937311e2e07 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Sat, 21 Sep 2024 12:22:24 +0900 Subject: [PATCH 14/34] chore: removed logs --- src/github/handlers/push-event.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index 0dde77e..3282267 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -81,11 +81,10 @@ export async function handleActionValidationWorkflowCompleted(context: GitHubCon return; } - const { rawData, path } = state.additionalProperties ?? {}; + const { rawData, path } = stateValidation; try { if (errors.length) { const body = []; - console.log("+++", JSON.stringify(state, null, 2)); body.push(`@${state.eventPayload.sender?.login} Configuration is invalid.\n`); if (errors.length) { body.push( From 17d696ef7be73b626db35a5536f2c970a891e0dc Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Sat, 21 Sep 2024 13:04:16 +0900 Subject: [PATCH 15/34] chore: error on unreachable endpoint --- src/github/handlers/push-event.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index 3282267..ef73c2a 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -9,7 +9,7 @@ import { Value, ValueErrorType } from "@sinclair/typebox/value"; import { StateValidation, stateValidationSchema } from "../types/state-validation-payload"; function constructErrorBody( - errors: Iterable | ValueError[] | YAML.YAMLError[], + errors: Iterable | (YAML.YAMLError | ValueError)[], rawData: string | null, repository: GitHubContext<"push">["payload"]["repository"], after: string @@ -119,7 +119,7 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { if (repository.owner) { const { config, errors: configurationErrors, rawData } = await getConfigurationFromRepo(context, repository.name, repository.owner.login); - const errors = []; + const errors: (ValueError | YAML.YAMLError)[] = []; // TODO test unreachable endpoints if (!configurationErrors && config) { for (let i = 0; i < config.plugins.length; ++i) { @@ -133,9 +133,20 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { const inputs = new PluginInput(context.eventHandler, stateId, context.key, payload, args, token, ref); if (!isGithubPluginObject) { - const response = await dispatchWorker(`${plugin}/manifest`, await inputs.getWorkerInputs()); - if (response.errors) { - errors.push(...response.errors.map((err) => ({ ...err, path: `plugins/${i}/uses/${j}/with${err.path}` }))); + try { + const response = await dispatchWorker(`${plugin}/manifest`, await inputs.getWorkerInputs()); + if (response.errors) { + errors.push(...response.errors.map((err) => ({ ...err, path: `plugins/${i}/uses/${j}/with${err.path}` }))); + } + } catch (e) { + console.error("Failed to reach plugin endpoint", e); + errors.push({ + path: `plugins/${i}/uses/${j}`, + message: "Failed to reach plugin endpoint", + value: plugin, + type: 0, + schema: stateValidationSchema, + }); } } else { await eventHandler.pluginChainState.put(stateId, { From d605dcb5ecb0ae8dd51d4ca4ef23c53b8a94d809 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Sat, 21 Sep 2024 14:09:15 +0900 Subject: [PATCH 16/34] chore: split error configuration --- src/github/handlers/push-event.ts | 122 +++++++++++++++++------------- 1 file changed, 69 insertions(+), 53 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index ef73c2a..5ff93a0 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -4,7 +4,7 @@ import YAML, { LineCounter, Node, YAMLError } from "yaml"; import { ValueError } from "typebox-validators"; import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/workflow-dispatch"; import { PluginInput, PluginOutput, pluginOutputSchema } from "../types/plugin"; -import { isGithubPlugin } from "../types/plugin-configuration"; +import { isGithubPlugin, PluginConfiguration } from "../types/plugin-configuration"; import { Value, ValueErrorType } from "@sinclair/typebox/value"; import { StateValidation, stateValidationSchema } from "../types/state-validation-payload"; @@ -108,8 +108,74 @@ export async function handleActionValidationWorkflowCompleted(context: GitHubCon } } +async function checkPluginConfigurations(context: GitHubContext<"push">, config: PluginConfiguration, rawData: string | null) { + const { payload, eventHandler } = context; + const errors: (ValueError | YAML.YAMLError)[] = []; + + for (let i = 0; i < config.plugins.length; ++i) { + const { uses } = config.plugins[i]; + for (let j = 0; j < uses.length; ++j) { + const { plugin, with: args } = uses[j]; + const isGithubPluginObject = isGithubPlugin(plugin); + const stateId = crypto.randomUUID(); + const token = payload.installation ? await eventHandler.getToken(payload.installation.id) : ""; + const ref = isGithubPluginObject ? (plugin.ref ?? (await getDefaultBranch(context, plugin.owner, plugin.repo))) : plugin; + const inputs = new PluginInput(context.eventHandler, stateId, context.key, payload, args, token, ref); + + if (!isGithubPluginObject) { + try { + const response = await dispatchWorker(`${plugin}/manifest`, await inputs.getWorkerInputs()); + if (response.errors) { + errors.push(...response.errors.map((err) => ({ ...err, path: `plugins/${i}/uses/${j}/with${err.path}` }))); + } + } catch (e) { + errors.push({ + path: `plugins/${i}/uses/${j}`, + message: `Failed to reach plugin endpoint: ${e}`, + value: plugin, + type: 0, + schema: stateValidationSchema, + }); + } + } else { + try { + await dispatchWorkflow(context, { + owner: plugin.owner, + repository: plugin.repo, + workflowId: "validate-schema.yml", + ref: plugin.ref, + inputs: inputs.getWorkflowInputs(), + }); + await eventHandler.pluginChainState.put(stateId, { + eventPayload: payload, + currentPlugin: 0, + eventId: "", + eventName: "push", + inputs: [inputs], + outputs: new Array(uses.length), + pluginChain: uses, + additionalProperties: { + rawData, + path: `plugins/${i}/uses/${j}/with`, + }, + }); + } catch (e) { + errors.push({ + path: `plugins/${i}/uses/${j}`, + message: `Failed to reach plugin action: ${e}`, + value: JSON.stringify(plugin), + type: 0, + schema: stateValidationSchema, + }); + } + } + } + } + return errors; +} + export default async function handlePushEvent(context: GitHubContext<"push">) { - const { octokit, payload, eventHandler } = context; + const { octokit, payload } = context; const { repository, commits, after } = payload; const didConfigurationFileChange = commits.some((commit) => commit.modified?.includes(CONFIG_FULL_PATH) || commit.added?.includes(CONFIG_FULL_PATH)); @@ -120,58 +186,8 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { if (repository.owner) { const { config, errors: configurationErrors, rawData } = await getConfigurationFromRepo(context, repository.name, repository.owner.login); const errors: (ValueError | YAML.YAMLError)[] = []; - // TODO test unreachable endpoints if (!configurationErrors && config) { - for (let i = 0; i < config.plugins.length; ++i) { - const { uses } = config.plugins[i]; - for (let j = 0; j < uses.length; ++j) { - const { plugin, with: args } = uses[j]; - const isGithubPluginObject = isGithubPlugin(plugin); - const stateId = crypto.randomUUID(); - const token = payload.installation ? await eventHandler.getToken(payload.installation.id) : ""; - const ref = isGithubPluginObject ? (plugin.ref ?? (await getDefaultBranch(context, plugin.owner, plugin.repo))) : plugin; - const inputs = new PluginInput(context.eventHandler, stateId, context.key, payload, args, token, ref); - - if (!isGithubPluginObject) { - try { - const response = await dispatchWorker(`${plugin}/manifest`, await inputs.getWorkerInputs()); - if (response.errors) { - errors.push(...response.errors.map((err) => ({ ...err, path: `plugins/${i}/uses/${j}/with${err.path}` }))); - } - } catch (e) { - console.error("Failed to reach plugin endpoint", e); - errors.push({ - path: `plugins/${i}/uses/${j}`, - message: "Failed to reach plugin endpoint", - value: plugin, - type: 0, - schema: stateValidationSchema, - }); - } - } else { - await eventHandler.pluginChainState.put(stateId, { - eventPayload: payload, - currentPlugin: 0, - eventId: "", - eventName: "push", - inputs: [inputs], - outputs: new Array(uses.length), - pluginChain: uses, - additionalProperties: { - rawData, - path: `plugins/${i}/uses/${j}/with`, - }, - }); - await dispatchWorkflow(context, { - owner: plugin.owner, - repository: plugin.repo, - workflowId: "validate-schema.yml", - ref: plugin.ref, - inputs: inputs.getWorkflowInputs(), - }); - } - } - } + errors.push(...(await checkPluginConfigurations(context, config, rawData))); } else if (configurationErrors) { errors.push(...configurationErrors); } From fa0a7adf2747bf178d57d04d85e3ca40ee6b7555 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Sat, 21 Sep 2024 14:11:24 +0900 Subject: [PATCH 17/34] chore: fix knip --- src/github/types/state-validation-payload.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/github/types/state-validation-payload.ts b/src/github/types/state-validation-payload.ts index a54ef49..5488304 100644 --- a/src/github/types/state-validation-payload.ts +++ b/src/github/types/state-validation-payload.ts @@ -1,5 +1,4 @@ import { StaticDecode, Type } from "@sinclair/typebox"; -import { StandardValidator } from "typebox-validators"; export const stateValidationSchema = Type.Object({ /** @@ -12,6 +11,4 @@ export const stateValidationSchema = Type.Object({ path: Type.String(), }); -export const stateValidationValidator = new StandardValidator(stateValidationSchema); - export type StateValidation = StaticDecode; From 072288196b0f9e44b27794868f781039e415f37c Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Sat, 21 Sep 2024 14:21:05 +0900 Subject: [PATCH 18/34] chore: fix types --- src/github/handlers/push-event.ts | 13 +++++++------ src/github/types/state-validation-payload.ts | 13 +++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index 5ff93a0..936d446 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -3,10 +3,10 @@ import { CONFIG_FULL_PATH, getConfigurationFromRepo } from "../utils/config"; import YAML, { LineCounter, Node, YAMLError } from "yaml"; import { ValueError } from "typebox-validators"; import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/workflow-dispatch"; -import { PluginInput, PluginOutput, pluginOutputSchema } from "../types/plugin"; +import { PluginChainState, PluginInput, PluginOutput, pluginOutputSchema } from "../types/plugin"; import { isGithubPlugin, PluginConfiguration } from "../types/plugin-configuration"; import { Value, ValueErrorType } from "@sinclair/typebox/value"; -import { StateValidation, stateValidationSchema } from "../types/state-validation-payload"; +import { pluginValidationResponseSchema, StateValidation, stateValidationSchema } from "../types/state-validation-payload"; function constructErrorBody( errors: Iterable | (YAML.YAMLError | ValueError)[], @@ -60,7 +60,7 @@ export async function handleActionValidationWorkflowCompleted(context: GitHubCon throw error; } - const state = await context.eventHandler.pluginChainState.get(pluginOutput.state_id); + const state = (await context.eventHandler.pluginChainState.get(pluginOutput.state_id)) as PluginChainState<"push">; if (!state) { console.error(`[handleActionValidationWorkflowCompleted]: No state found for plugin chain ${pluginOutput.state_id}`); @@ -83,7 +83,7 @@ export async function handleActionValidationWorkflowCompleted(context: GitHubCon const { rawData, path } = stateValidation; try { - if (errors.length) { + if (errors.length && state.eventPayload.repository.owner) { const body = []; body.push(`@${state.eventPayload.sender?.login} Configuration is invalid.\n`); if (errors.length) { @@ -125,8 +125,9 @@ async function checkPluginConfigurations(context: GitHubContext<"push">, config: if (!isGithubPluginObject) { try { const response = await dispatchWorker(`${plugin}/manifest`, await inputs.getWorkerInputs()); - if (response.errors) { - errors.push(...response.errors.map((err) => ({ ...err, path: `plugins/${i}/uses/${j}/with${err.path}` }))); + const decodedResponse = Value.Decode(pluginValidationResponseSchema, response); + if (decodedResponse.errors) { + errors.push(...decodedResponse.errors.map((err) => ({ ...err, path: `plugins/${i}/uses/${j}/with${err.path}` }))); } } catch (e) { errors.push({ diff --git a/src/github/types/state-validation-payload.ts b/src/github/types/state-validation-payload.ts index 5488304..76f03d7 100644 --- a/src/github/types/state-validation-payload.ts +++ b/src/github/types/state-validation-payload.ts @@ -11,4 +11,17 @@ export const stateValidationSchema = Type.Object({ path: Type.String(), }); +const validationErrorSchema = Type.Object({ + path: Type.String(), + message: Type.String(), + type: Type.Number(), + value: Type.Any(), + schema: Type.Any(), +}); + +export const pluginValidationResponseSchema = Type.Object({ + message: Type.String(), + errors: Type.Array(validationErrorSchema), +}); + export type StateValidation = StaticDecode; From f4fe02718b80babba0aafc2c7b5153491e7ef0f0 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Sat, 21 Sep 2024 14:27:31 +0900 Subject: [PATCH 19/34] chore: fix tests --- tests/events.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/events.test.ts b/tests/events.test.ts index bf4ee9e..b5f16d7 100644 --- a/tests/events.test.ts +++ b/tests/events.test.ts @@ -30,7 +30,7 @@ afterAll(() => { describe("Event related tests", () => { beforeEach(() => { server.use( - http.get("https://plugin-a.internal/manifest.json", () => + http.get("https://plugin-a.internal/manifest", () => HttpResponse.json({ name: "plugin", commands: { From 45c9ae4e55f09c8bc699479843dc3c8cdf47a76e Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Mon, 23 Sep 2024 12:03:29 +0900 Subject: [PATCH 20/34] chore: joined comments --- src/github/handlers/push-event.ts | 73 ++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index 936d446..60e1b0b 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -48,7 +48,7 @@ function constructErrorBody( } export async function handleActionValidationWorkflowCompleted(context: GitHubContext<"repository_dispatch">) { - const { octokit, payload } = context; + const { payload } = context; const { client_payload } = payload; let pluginOutput: PluginOutput; let stateValidation: StateValidation; @@ -85,7 +85,6 @@ export async function handleActionValidationWorkflowCompleted(context: GitHubCon try { if (errors.length && state.eventPayload.repository.owner) { const body = []; - body.push(`@${state.eventPayload.sender?.login} Configuration is invalid.\n`); if (errors.length) { body.push( ...constructErrorBody( @@ -96,18 +95,57 @@ export async function handleActionValidationWorkflowCompleted(context: GitHubCon ) ); } - await octokit.rest.repos.createCommitComment({ - owner: state.eventPayload.repository.owner.login, - repo: state.eventPayload.repository.name, - commit_sha: state.eventPayload.after as string, - body: body.join(""), - }); + await createCommitComment( + context, + { + owner: state.eventPayload.repository.owner.login, + repo: state.eventPayload.repository.name, + commitSha: state.eventPayload.after as string, + userLogin: state.eventPayload.sender?.login, + }, + body + ); } } catch (e) { console.error("handleActionValidationWorkflowCompleted", e); } } +async function createCommitComment( + context: GitHubContext, + { owner, repo, commitSha, userLogin }: { owner: string; repo: string; commitSha: string; userLogin?: string }, + body: string[] +) { + const { octokit } = context; + + const commit = ( + await octokit.rest.repos.listCommentsForCommit({ + owner: owner, + repo: repo, + commit_sha: commitSha, + }) + ).data + .filter((o) => o.user?.type === "Bot") + .pop(); + if (commit) { + await octokit.rest.repos.updateCommitComment({ + owner: owner, + repo: repo, + commit_sha: commitSha, + comment_id: commit.id, + body: `${commit.body}\n${body.join("")}`, + }); + } else { + body.unshift(`@${userLogin} Configuration is invalid.\n`); + await octokit.rest.repos.createCommitComment({ + owner: owner, + repo: repo, + commit_sha: commitSha, + body: body.join(""), + }); + } +} + async function checkPluginConfigurations(context: GitHubContext<"push">, config: PluginConfiguration, rawData: string | null) { const { payload, eventHandler } = context; const errors: (ValueError | YAML.YAMLError)[] = []; @@ -176,7 +214,7 @@ async function checkPluginConfigurations(context: GitHubContext<"push">, config: } export default async function handlePushEvent(context: GitHubContext<"push">) { - const { octokit, payload } = context; + const { payload } = context; const { repository, commits, after } = payload; const didConfigurationFileChange = commits.some((commit) => commit.modified?.includes(CONFIG_FULL_PATH) || commit.added?.includes(CONFIG_FULL_PATH)); @@ -195,14 +233,17 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { try { if (errors.length) { const body = []; - body.push(`@${payload.sender?.login} Configuration is invalid.\n`); body.push(...constructErrorBody(errors, rawData, repository, after)); - await octokit.rest.repos.createCommitComment({ - owner: repository.owner.login, - repo: repository.name, - commit_sha: after, - body: body.join(""), - }); + await createCommitComment( + context, + { + owner: repository.owner.login, + repo: repository.name, + commitSha: after, + userLogin: payload.sender?.login, + }, + body + ); } } catch (e) { console.error("handlePushEventError", e); From 4fd3ea61549679fbc2b996db544ac95e52bbc7fe Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Mon, 23 Sep 2024 18:11:54 +0900 Subject: [PATCH 21/34] chore: made errors optional --- src/github/types/state-validation-payload.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/github/types/state-validation-payload.ts b/src/github/types/state-validation-payload.ts index 76f03d7..dd78f8b 100644 --- a/src/github/types/state-validation-payload.ts +++ b/src/github/types/state-validation-payload.ts @@ -21,7 +21,7 @@ const validationErrorSchema = Type.Object({ export const pluginValidationResponseSchema = Type.Object({ message: Type.String(), - errors: Type.Array(validationErrorSchema), + errors: Type.Optional(Type.Array(validationErrorSchema)), }); export type StateValidation = StaticDecode; From c9c66f7c7ad63742ffeeda80525ebcc6adcdd4b4 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Mon, 23 Sep 2024 20:57:07 +0900 Subject: [PATCH 22/34] chore: made message optional --- src/github/types/state-validation-payload.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/github/types/state-validation-payload.ts b/src/github/types/state-validation-payload.ts index dd78f8b..bf78e3b 100644 --- a/src/github/types/state-validation-payload.ts +++ b/src/github/types/state-validation-payload.ts @@ -20,7 +20,7 @@ const validationErrorSchema = Type.Object({ }); export const pluginValidationResponseSchema = Type.Object({ - message: Type.String(), + message: Type.Optional(Type.String()), errors: Type.Optional(Type.Array(validationErrorSchema)), }); From 3e99da199758ec45345e6c375c20ec89719496c8 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Mon, 23 Sep 2024 21:32:04 +0900 Subject: [PATCH 23/34] chore: made type optional --- src/github/types/state-validation-payload.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/github/types/state-validation-payload.ts b/src/github/types/state-validation-payload.ts index bf78e3b..161ae32 100644 --- a/src/github/types/state-validation-payload.ts +++ b/src/github/types/state-validation-payload.ts @@ -14,9 +14,9 @@ export const stateValidationSchema = Type.Object({ const validationErrorSchema = Type.Object({ path: Type.String(), message: Type.String(), - type: Type.Number(), + type: Type.Number({ default: 0 }), value: Type.Any(), - schema: Type.Any(), + schema: Type.Any({ default: {} }), }); export const pluginValidationResponseSchema = Type.Object({ From 106fc0cb5294378e6f7a1e70355bcff71c0dc658 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Mon, 23 Sep 2024 21:55:32 +0900 Subject: [PATCH 24/34] chore: added defaults to errors --- src/github/handlers/push-event.ts | 13 ++++++-- src/github/types/state-validation-payload.ts | 31 +++++++++++++------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index 60e1b0b..d26a839 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -6,7 +6,7 @@ import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/wor import { PluginChainState, PluginInput, PluginOutput, pluginOutputSchema } from "../types/plugin"; import { isGithubPlugin, PluginConfiguration } from "../types/plugin-configuration"; import { Value, ValueErrorType } from "@sinclair/typebox/value"; -import { pluginValidationResponseSchema, StateValidation, stateValidationSchema } from "../types/state-validation-payload"; +import { pluginValidationResponseSchema, StateValidation, stateValidationErrorSchemaValidator, stateValidationSchema } from "../types/state-validation-payload"; function constructErrorBody( errors: Iterable | (YAML.YAMLError | ValueError)[], @@ -163,8 +163,15 @@ async function checkPluginConfigurations(context: GitHubContext<"push">, config: if (!isGithubPluginObject) { try { const response = await dispatchWorker(`${plugin}/manifest`, await inputs.getWorkerInputs()); - const decodedResponse = Value.Decode(pluginValidationResponseSchema, response); - if (decodedResponse.errors) { + const responseWithDefaults = Value.Default(pluginValidationResponseSchema, response) as StateValidation; + if (!stateValidationErrorSchemaValidator.test(responseWithDefaults)) { + console.error("Malformed response from the endpoints"); + for (const err of stateValidationErrorSchemaValidator.errors(responseWithDefaults)) { + console.error(err); + } + } + const decodedResponse = Value.Decode(pluginValidationResponseSchema, responseWithDefaults); + if (decodedResponse.errors.length) { errors.push(...decodedResponse.errors.map((err) => ({ ...err, path: `plugins/${i}/uses/${j}/with${err.path}` }))); } } catch (e) { diff --git a/src/github/types/state-validation-payload.ts b/src/github/types/state-validation-payload.ts index 161ae32..08b8cd2 100644 --- a/src/github/types/state-validation-payload.ts +++ b/src/github/types/state-validation-payload.ts @@ -1,4 +1,5 @@ import { StaticDecode, Type } from "@sinclair/typebox"; +import { StandardValidator } from "typebox-validators"; export const stateValidationSchema = Type.Object({ /** @@ -11,17 +12,25 @@ export const stateValidationSchema = Type.Object({ path: Type.String(), }); -const validationErrorSchema = Type.Object({ - path: Type.String(), - message: Type.String(), - type: Type.Number({ default: 0 }), - value: Type.Any(), - schema: Type.Any({ default: {} }), -}); +const validationErrorSchema = Type.Object( + { + path: Type.String({ default: "/" }), + message: Type.String(), + type: Type.Number({ default: 0 }), + value: Type.Any({ default: undefined }), + schema: Type.Any({ default: {} }), + }, + { default: {} } +); -export const pluginValidationResponseSchema = Type.Object({ - message: Type.Optional(Type.String()), - errors: Type.Optional(Type.Array(validationErrorSchema)), -}); +export const pluginValidationResponseSchema = Type.Object( + { + message: Type.Optional(Type.String()), + errors: Type.Array(validationErrorSchema, { default: [] }), + }, + { default: {} } +); + +export const stateValidationErrorSchemaValidator = new StandardValidator(validationErrorSchema); export type StateValidation = StaticDecode; From 1857db307465adbe698a7b5ff54c194298e233fb Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Mon, 23 Sep 2024 21:59:57 +0900 Subject: [PATCH 25/34] chore: added defaults to errors --- src/github/types/state-validation-payload.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/github/types/state-validation-payload.ts b/src/github/types/state-validation-payload.ts index 08b8cd2..76f0e75 100644 --- a/src/github/types/state-validation-payload.ts +++ b/src/github/types/state-validation-payload.ts @@ -31,6 +31,6 @@ export const pluginValidationResponseSchema = Type.Object( { default: {} } ); -export const stateValidationErrorSchemaValidator = new StandardValidator(validationErrorSchema); +export const stateValidationErrorSchemaValidator = new StandardValidator(pluginValidationResponseSchema); export type StateValidation = StaticDecode; From 2f23b6d52bdc6a40a0e171748c7b988653ced9b3 Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:42:10 +0900 Subject: [PATCH 26/34] chore: reduced nesting of conditions --- src/github/handlers/push-event.ts | 56 +++++++++++++++---------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index d26a839..b53d500 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -226,35 +226,35 @@ export default async function handlePushEvent(context: GitHubContext<"push">) { const didConfigurationFileChange = commits.some((commit) => commit.modified?.includes(CONFIG_FULL_PATH) || commit.added?.includes(CONFIG_FULL_PATH)); - if (didConfigurationFileChange) { - console.log("Configuration file changed, will run configuration checks."); + if (!didConfigurationFileChange || !repository.owner) { + return; + } - if (repository.owner) { - const { config, errors: configurationErrors, rawData } = await getConfigurationFromRepo(context, repository.name, repository.owner.login); - const errors: (ValueError | YAML.YAMLError)[] = []; - if (!configurationErrors && config) { - errors.push(...(await checkPluginConfigurations(context, config, rawData))); - } else if (configurationErrors) { - errors.push(...configurationErrors); - } - try { - if (errors.length) { - const body = []; - body.push(...constructErrorBody(errors, rawData, repository, after)); - await createCommitComment( - context, - { - owner: repository.owner.login, - repo: repository.name, - commitSha: after, - userLogin: payload.sender?.login, - }, - body - ); - } - } catch (e) { - console.error("handlePushEventError", e); - } + console.log("Configuration file changed, will run configuration checks."); + + const { config, errors: configurationErrors, rawData } = await getConfigurationFromRepo(context, repository.name, repository.owner.login); + const errors: (ValueError | YAML.YAMLError)[] = []; + if (!configurationErrors && config) { + errors.push(...(await checkPluginConfigurations(context, config, rawData))); + } else if (configurationErrors) { + errors.push(...configurationErrors); + } + try { + if (errors.length) { + const body = []; + body.push(...constructErrorBody(errors, rawData, repository, after)); + await createCommitComment( + context, + { + owner: repository.owner.login, + repo: repository.name, + commitSha: after, + userLogin: payload.sender?.login, + }, + body + ); } + } catch (e) { + console.error("handlePushEventError", e); } } From bef8f71c589e53b32e7d7f86c6b54cebcc0ee5ac Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:43:27 +0900 Subject: [PATCH 27/34] chore: renamed commit to comment --- src/github/handlers/push-event.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index b53d500..7a3b3f7 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -118,7 +118,7 @@ async function createCommitComment( ) { const { octokit } = context; - const commit = ( + const comment = ( await octokit.rest.repos.listCommentsForCommit({ owner: owner, repo: repo, @@ -127,13 +127,13 @@ async function createCommitComment( ).data .filter((o) => o.user?.type === "Bot") .pop(); - if (commit) { + if (comment) { await octokit.rest.repos.updateCommitComment({ owner: owner, repo: repo, commit_sha: commitSha, - comment_id: commit.id, - body: `${commit.body}\n${body.join("")}`, + comment_id: comment.id, + body: `${comment.body}\n${body.join("")}`, }); } else { body.unshift(`@${userLogin} Configuration is invalid.\n`); From 883e8cad09eb7c43176f70eacf1b6cce81e4403f Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:51:12 +0900 Subject: [PATCH 28/34] chore: renamed validator --- src/github/handlers/push-event.ts | 11 ++++++++--- src/github/types/state-validation-payload.ts | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index 7a3b3f7..d2b84b5 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -6,7 +6,12 @@ import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/wor import { PluginChainState, PluginInput, PluginOutput, pluginOutputSchema } from "../types/plugin"; import { isGithubPlugin, PluginConfiguration } from "../types/plugin-configuration"; import { Value, ValueErrorType } from "@sinclair/typebox/value"; -import { pluginValidationResponseSchema, StateValidation, stateValidationErrorSchemaValidator, stateValidationSchema } from "../types/state-validation-payload"; +import { + pluginValidationResponseSchema, + StateValidation, + pluginValidationResponseSchemaValidator, + stateValidationSchema, +} from "../types/state-validation-payload"; function constructErrorBody( errors: Iterable | (YAML.YAMLError | ValueError)[], @@ -164,9 +169,9 @@ async function checkPluginConfigurations(context: GitHubContext<"push">, config: try { const response = await dispatchWorker(`${plugin}/manifest`, await inputs.getWorkerInputs()); const responseWithDefaults = Value.Default(pluginValidationResponseSchema, response) as StateValidation; - if (!stateValidationErrorSchemaValidator.test(responseWithDefaults)) { + if (!pluginValidationResponseSchemaValidator.test(responseWithDefaults)) { console.error("Malformed response from the endpoints"); - for (const err of stateValidationErrorSchemaValidator.errors(responseWithDefaults)) { + for (const err of pluginValidationResponseSchemaValidator.errors(responseWithDefaults)) { console.error(err); } } diff --git a/src/github/types/state-validation-payload.ts b/src/github/types/state-validation-payload.ts index 76f0e75..a38f095 100644 --- a/src/github/types/state-validation-payload.ts +++ b/src/github/types/state-validation-payload.ts @@ -31,6 +31,6 @@ export const pluginValidationResponseSchema = Type.Object( { default: {} } ); -export const stateValidationErrorSchemaValidator = new StandardValidator(pluginValidationResponseSchema); +export const pluginValidationResponseSchemaValidator = new StandardValidator(pluginValidationResponseSchema); export type StateValidation = StaticDecode; From 60bf35916df10581f733caa67df4432548d5b71f Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Tue, 1 Oct 2024 01:08:06 +0900 Subject: [PATCH 29/34] chore: using json cloudflare version --- bun.lockb | Bin 434290 -> 434671 bytes package.json | 1 + src/github/handlers/push-event.ts | 167 ++++----------------- src/github/handlers/repository-dispatch.ts | 11 +- src/types/manifest.ts | 1 + 5 files changed, 38 insertions(+), 142 deletions(-) diff --git a/bun.lockb b/bun.lockb index f592a85bc1207cd806dbd3ca72ee2f23191a5381..d7aa771eff192d07efc18f5dbc32f8cf8660e7c7 100755 GIT binary patch delta 83280 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 Qc<(!bReRrwpS{fg0nZ;5ng9R* diff --git a/package.json b/package.json index 4b94720..9601a70 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "dependencies": { "@actions/core": "1.10.1", "@actions/github": "6.0.0", + "@cfworker/json-schema": "2.0.1", "@octokit/auth-app": "7.1.0", "@octokit/core": "6.1.2", "@octokit/plugin-paginate-rest": "11.3.3", diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index d2b84b5..8d18020 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -1,17 +1,12 @@ +import { Validator } from "@cfworker/json-schema"; +import { ValueErrorType } from "@sinclair/typebox/value"; +import { ValueError } from "typebox-validators"; +import YAML, { LineCounter, Node, YAMLError } from "yaml"; import { GitHubContext } from "../github-context"; +import { PluginConfiguration } from "../types/plugin-configuration"; +import { stateValidationSchema } from "../types/state-validation-payload"; import { CONFIG_FULL_PATH, getConfigurationFromRepo } from "../utils/config"; -import YAML, { LineCounter, Node, YAMLError } from "yaml"; -import { ValueError } from "typebox-validators"; -import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/workflow-dispatch"; -import { PluginChainState, PluginInput, PluginOutput, pluginOutputSchema } from "../types/plugin"; -import { isGithubPlugin, PluginConfiguration } from "../types/plugin-configuration"; -import { Value, ValueErrorType } from "@sinclair/typebox/value"; -import { - pluginValidationResponseSchema, - StateValidation, - pluginValidationResponseSchemaValidator, - stateValidationSchema, -} from "../types/state-validation-payload"; +import { getManifest } from "../utils/plugins"; function constructErrorBody( errors: Iterable | (YAML.YAMLError | ValueError)[], @@ -52,70 +47,6 @@ function constructErrorBody( return body; } -export async function handleActionValidationWorkflowCompleted(context: GitHubContext<"repository_dispatch">) { - const { payload } = context; - const { client_payload } = payload; - let pluginOutput: PluginOutput; - let stateValidation: StateValidation; - - try { - pluginOutput = Value.Decode(pluginOutputSchema, client_payload); - } catch (error) { - console.error("[handleActionValidationWorkflowCompleted]: Cannot decode plugin output", error); - throw error; - } - - const state = (await context.eventHandler.pluginChainState.get(pluginOutput.state_id)) as PluginChainState<"push">; - - if (!state) { - console.error(`[handleActionValidationWorkflowCompleted]: No state found for plugin chain ${pluginOutput.state_id}`); - return; - } - - console.log("Received Action output result for validation, will process.", pluginOutput.output); - - const errors = pluginOutput.output.errors as ValueError[]; - try { - stateValidation = Value.Decode(stateValidationSchema, state.additionalProperties); - } catch (e) { - console.error(`[handleActionValidationWorkflowCompleted]: Cannot decode state properties`); - throw e; - } - if (!stateValidation) { - console.error(`[handleActionValidationWorkflowCompleted]: State validation is invalid for ${pluginOutput.state_id}`); - return; - } - - const { rawData, path } = stateValidation; - try { - if (errors.length && state.eventPayload.repository.owner) { - const body = []; - if (errors.length) { - body.push( - ...constructErrorBody( - errors.map((err) => ({ ...err, path: `${path}${err.path}` })), - rawData as string, - state.eventPayload.repository as GitHubContext<"push">["payload"]["repository"], - state.eventPayload.after as string - ) - ); - } - await createCommitComment( - context, - { - owner: state.eventPayload.repository.owner.login, - repo: state.eventPayload.repository.name, - commitSha: state.eventPayload.after as string, - userLogin: state.eventPayload.sender?.login, - }, - body - ); - } - } catch (e) { - console.error("handleActionValidationWorkflowCompleted", e); - } -} - async function createCommitComment( context: GitHubContext, { owner, repo, commitSha, userLogin }: { owner: string; repo: string; commitSha: string; userLogin?: string }, @@ -152,72 +83,38 @@ async function createCommitComment( } async function checkPluginConfigurations(context: GitHubContext<"push">, config: PluginConfiguration, rawData: string | null) { - const { payload, eventHandler } = context; const errors: (ValueError | YAML.YAMLError)[] = []; + const doc = rawData ? YAML.parseDocument(rawData) : null; for (let i = 0; i < config.plugins.length; ++i) { const { uses } = config.plugins[i]; for (let j = 0; j < uses.length; ++j) { const { plugin, with: args } = uses[j]; - const isGithubPluginObject = isGithubPlugin(plugin); - const stateId = crypto.randomUUID(); - const token = payload.installation ? await eventHandler.getToken(payload.installation.id) : ""; - const ref = isGithubPluginObject ? (plugin.ref ?? (await getDefaultBranch(context, plugin.owner, plugin.repo))) : plugin; - const inputs = new PluginInput(context.eventHandler, stateId, context.key, payload, args, token, ref); - - if (!isGithubPluginObject) { - try { - const response = await dispatchWorker(`${plugin}/manifest`, await inputs.getWorkerInputs()); - const responseWithDefaults = Value.Default(pluginValidationResponseSchema, response) as StateValidation; - if (!pluginValidationResponseSchemaValidator.test(responseWithDefaults)) { - console.error("Malformed response from the endpoints"); - for (const err of pluginValidationResponseSchemaValidator.errors(responseWithDefaults)) { - console.error(err); - } - } - const decodedResponse = Value.Decode(pluginValidationResponseSchema, responseWithDefaults); - if (decodedResponse.errors.length) { - errors.push(...decodedResponse.errors.map((err) => ({ ...err, path: `plugins/${i}/uses/${j}/with${err.path}` }))); - } - } catch (e) { - errors.push({ - path: `plugins/${i}/uses/${j}`, - message: `Failed to reach plugin endpoint: ${e}`, - value: plugin, - type: 0, - schema: stateValidationSchema, - }); - } + const manifest = await getManifest(context, plugin); + if (!manifest?.configuration) { + errors.push({ + path: `plugins/${i}/uses/${j}`, + message: `Failed to fetch the manifest configuration.`, + value: plugin, + type: 0, + schema: stateValidationSchema, + }); } else { - try { - await dispatchWorkflow(context, { - owner: plugin.owner, - repository: plugin.repo, - workflowId: "validate-schema.yml", - ref: plugin.ref, - inputs: inputs.getWorkflowInputs(), - }); - await eventHandler.pluginChainState.put(stateId, { - eventPayload: payload, - currentPlugin: 0, - eventId: "", - eventName: "push", - inputs: [inputs], - outputs: new Array(uses.length), - pluginChain: uses, - additionalProperties: { - rawData, - path: `plugins/${i}/uses/${j}/with`, - }, - }); - } catch (e) { - errors.push({ - path: `plugins/${i}/uses/${j}`, - message: `Failed to reach plugin action: ${e}`, - value: JSON.stringify(plugin), - type: 0, - schema: stateValidationSchema, - }); + const validator = new Validator(manifest.configuration, "7", false); + const result = validator.validate(args); + + if (!result.valid) { + for (const error of result.errors) { + const path = error.instanceLocation.replace("#", `plugins/${i}/uses/${j}/with`); + const value = doc?.getIn(path.split("/").filter((o) => o)); + errors.push({ + path, + message: error.error, + value, + type: 0, + schema: stateValidationSchema, + }); + } } } } diff --git a/src/github/handlers/repository-dispatch.ts b/src/github/handlers/repository-dispatch.ts index ae201f1..2441d58 100644 --- a/src/github/handlers/repository-dispatch.ts +++ b/src/github/handlers/repository-dispatch.ts @@ -1,16 +1,13 @@ -import { GitHubContext } from "../github-context"; -import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/workflow-dispatch"; import { Value } from "@sinclair/typebox/value"; -import { PluginInput, PluginChainState, expressionRegex, pluginOutputSchema } from "../types/plugin"; +import { GitHubContext } from "../github-context"; +import { expressionRegex, PluginChainState, PluginInput, pluginOutputSchema } from "../types/plugin"; import { isGithubPlugin } from "../types/plugin-configuration"; -import { handleActionValidationWorkflowCompleted } from "./push-event"; +import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/workflow-dispatch"; export async function repositoryDispatch(context: GitHubContext<"repository_dispatch">) { console.log("Repository dispatch event received", context.payload.client_payload); - if (context.payload.action === "configuration_validation") { - return handleActionValidationWorkflowCompleted(context); - } else if (context.payload.action !== "return_data_to_ubiquibot_kernel") { + if (context.payload.action !== "return_data_to_ubiquibot_kernel") { console.log("Skipping non-ubiquibot event"); return; } diff --git a/src/types/manifest.ts b/src/types/manifest.ts index 6dcd92c..28ad2e9 100644 --- a/src/types/manifest.ts +++ b/src/types/manifest.ts @@ -14,6 +14,7 @@ export const manifestSchema = T.Object({ description: T.Optional(T.String({ default: "" })), commands: T.Optional(T.Record(T.String(), commandSchema, { default: {} })), "ubiquity:listeners": T.Optional(T.Array(runEvent, { default: [] })), + configuration: T.Optional(T.Record(T.String(), T.Any(), { default: {} })), }); export const manifestValidator = new StandardValidator(manifestSchema); From cee9a553364c0d6357aae29e3c16c9f27ec052f4 Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Tue, 1 Oct 2024 01:12:03 +0900 Subject: [PATCH 30/34] chore: removed unused schema --- src/github/handlers/push-event.ts | 7 ++-- src/github/types/state-validation-payload.ts | 36 -------------------- 2 files changed, 3 insertions(+), 40 deletions(-) delete mode 100644 src/github/types/state-validation-payload.ts diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index 8d18020..642f81c 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -3,8 +3,7 @@ import { ValueErrorType } from "@sinclair/typebox/value"; import { ValueError } from "typebox-validators"; import YAML, { LineCounter, Node, YAMLError } from "yaml"; import { GitHubContext } from "../github-context"; -import { PluginConfiguration } from "../types/plugin-configuration"; -import { stateValidationSchema } from "../types/state-validation-payload"; +import { configSchema, PluginConfiguration } from "../types/plugin-configuration"; import { CONFIG_FULL_PATH, getConfigurationFromRepo } from "../utils/config"; import { getManifest } from "../utils/plugins"; @@ -97,7 +96,7 @@ async function checkPluginConfigurations(context: GitHubContext<"push">, config: message: `Failed to fetch the manifest configuration.`, value: plugin, type: 0, - schema: stateValidationSchema, + schema: configSchema, }); } else { const validator = new Validator(manifest.configuration, "7", false); @@ -112,7 +111,7 @@ async function checkPluginConfigurations(context: GitHubContext<"push">, config: message: error.error, value, type: 0, - schema: stateValidationSchema, + schema: configSchema, }); } } diff --git a/src/github/types/state-validation-payload.ts b/src/github/types/state-validation-payload.ts deleted file mode 100644 index a38f095..0000000 --- a/src/github/types/state-validation-payload.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { StaticDecode, Type } from "@sinclair/typebox"; -import { StandardValidator } from "typebox-validators"; - -export const stateValidationSchema = Type.Object({ - /** - * The YAML raw data - */ - rawData: Type.String(), - /** - * Path to the YAML element in the document - */ - path: Type.String(), -}); - -const validationErrorSchema = Type.Object( - { - path: Type.String({ default: "/" }), - message: Type.String(), - type: Type.Number({ default: 0 }), - value: Type.Any({ default: undefined }), - schema: Type.Any({ default: {} }), - }, - { default: {} } -); - -export const pluginValidationResponseSchema = Type.Object( - { - message: Type.Optional(Type.String()), - errors: Type.Array(validationErrorSchema, { default: [] }), - }, - { default: {} } -); - -export const pluginValidationResponseSchemaValidator = new StandardValidator(pluginValidationResponseSchema); - -export type StateValidation = StaticDecode; From 4947196fc22272eb433c83531c9b2ca4838af978 Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Tue, 1 Oct 2024 01:13:29 +0900 Subject: [PATCH 31/34] chore: resolve conflict --- bun.lockb | Bin 434671 -> 434732 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bun.lockb b/bun.lockb index d7aa771eff192d07efc18f5dbc32f8cf8660e7c7..bc3ab857085494b46fb157d480a047889c2e287a 100755 GIT binary patch delta 75314 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 C{rtB8 From 062b2a597de9c0f5a5aabb0d11206f4cb1bc34d3 Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Tue, 1 Oct 2024 01:18:51 +0900 Subject: [PATCH 32/34] chore: fix tests --- tests/configuration.test.ts | 2 ++ tests/main.test.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/configuration.test.ts b/tests/configuration.test.ts index a5eaeed..331fa4b 100644 --- a/tests/configuration.test.ts +++ b/tests/configuration.test.ts @@ -111,6 +111,7 @@ describe("Configuration tests", () => { "ubiquity:example": "example", }, }, + configuration: {}, description: "", "ubiquity:listeners": [], }, @@ -122,6 +123,7 @@ describe("Configuration tests", () => { "ubiquity:example": "example", }, }, + configuration: {}, description: "", "ubiquity:listeners": [], }, diff --git a/tests/main.test.ts b/tests/main.test.ts index 3749fa2..8a3eb7d 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -37,7 +37,7 @@ afterAll(() => { describe("Worker tests", () => { beforeEach(() => { server.use( - http.get("https://plugin-a.internal/manifest.json", () => + http.get("https://plugin-a.internal/manifest", () => HttpResponse.json({ name: "plugin", commands: { From e6a23a0f49a72711450440bd7d7edb567d066fd4 Mon Sep 17 00:00:00 2001 From: gentlementlegen Date: Thu, 3 Oct 2024 20:20:53 +0900 Subject: [PATCH 33/34] chore: renamed return value string --- README.md | 2 +- src/github/handlers/repository-dispatch.ts | 2 +- src/sdk/actions.ts | 2 +- tests/sdk.test.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 31dfe44..2a8174d 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ const input: PluginInput = { Data is returned using the `repository_dispatch` event on the plugin's repository, and the output is structured within the `client_payload`. -The `event_type` must be set to `return_data_to_ubiquibot_kernel`. +The `event_type` must be set to `return-data-to-ubiquity-os-kernel`. ```typescript interface PluginOutput { diff --git a/src/github/handlers/repository-dispatch.ts b/src/github/handlers/repository-dispatch.ts index 2441d58..4f4b831 100644 --- a/src/github/handlers/repository-dispatch.ts +++ b/src/github/handlers/repository-dispatch.ts @@ -7,7 +7,7 @@ import { dispatchWorker, dispatchWorkflow, getDefaultBranch } from "../utils/wor export async function repositoryDispatch(context: GitHubContext<"repository_dispatch">) { console.log("Repository dispatch event received", context.payload.client_payload); - if (context.payload.action !== "return_data_to_ubiquibot_kernel") { + if (context.payload.action !== "return-data-to-ubiquity-os-kernel") { console.log("Skipping non-ubiquibot event"); return; } diff --git a/src/sdk/actions.ts b/src/sdk/actions.ts index 9479c4a..624a55e 100644 --- a/src/sdk/actions.ts +++ b/src/sdk/actions.ts @@ -110,7 +110,7 @@ async function returnDataToKernel(repoToken: string, stateId: string, output: ob await octokit.rest.repos.createDispatchEvent({ owner: github.context.repo.owner, repo: github.context.repo.repo, - event_type: "return_data_to_ubiquibot_kernel", + event_type: "return-data-to-ubiquity-os-kernel", client_payload: { state_id: stateId, output: output ? JSON.stringify(output) : null, diff --git a/tests/sdk.test.ts b/tests/sdk.test.ts index 8cc7df5..5bd8ac0 100644 --- a/tests/sdk.test.ts +++ b/tests/sdk.test.ts @@ -231,7 +231,7 @@ describe("SDK actions tests", () => { expect(setOutput).toHaveBeenCalledWith("result", { event: issueCommented.eventName }); expect(setFailed).not.toHaveBeenCalled(); expect(createDispatchEvent).toHaveBeenCalledWith({ - event_type: "return_data_to_ubiquibot_kernel", + event_type: "return-data-to-ubiquity-os-kernel", owner: "ubiquity", repo: "ubiquibot-kernel", client_payload: { From bce22fe2cdea44631d9f848e77a26276f5512e7d Mon Sep 17 00:00:00 2001 From: Mentlegen <9807008+gentlementlegen@users.noreply.github.com> Date: Fri, 4 Oct 2024 00:10:40 +0900 Subject: [PATCH 34/34] chore: change error output to yml highlighting --- src/github/handlers/push-event.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/github/handlers/push-event.ts b/src/github/handlers/push-event.ts index 642f81c..7ccf830 100644 --- a/src/github/handlers/push-event.ts +++ b/src/github/handlers/push-event.ts @@ -38,7 +38,7 @@ function constructErrorBody( message.push(`value: ${error.value}\n`); message.push(`message: ${error.message}`); } - body.push(`\n> \`\`\`\n`); + body.push(`\n> \`\`\`yml\n`); body.push(`> ${message.join("").replaceAll("\n", "\n> ")}`); body.push(`\n> \`\`\`\n\n`); }