Skip to content

Commit

Permalink
feat: workflow dispatch
Browse files Browse the repository at this point in the history
  • Loading branch information
whilefoo committed Feb 13, 2024
1 parent 89f7de7 commit b17af14
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 43 deletions.
5 changes: 4 additions & 1 deletion src/github/github-context.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { EmitterWebhookEvent as WebhookEvent, EmitterWebhookEventName as WebhookEventName } from "@octokit/webhooks";
import { customOctokit } from "./github-client";
import { GitHubEventHandler } from "./github-event-handler";

export class GitHubContext<T extends WebhookEventName = WebhookEventName> {
public key: WebhookEventName;
public name: WebhookEventName;
public id: string;
public payload: WebhookEvent<T>["payload"];
public octokit: InstanceType<typeof customOctokit>;
public eventHandler: InstanceType<typeof GitHubEventHandler>;

constructor(event: WebhookEvent<T>, octokit: InstanceType<typeof customOctokit>) {
constructor(eventHandler: InstanceType<typeof GitHubEventHandler>, event: WebhookEvent<T>, octokit: InstanceType<typeof customOctokit>) {
this.eventHandler = eventHandler;
this.name = event.name;
this.id = event.id;
this.payload = event.payload;
Expand Down
26 changes: 20 additions & 6 deletions src/github/github-event-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class GitHubEventHandler {

this.webhooks = new Webhooks<SimplifiedContext>({
secret: this._webhookSecret,
transform: this.transformEvent,
transform: (event) => this.transformEvent(event), // it is important to use an arrow function here to keep the context of `this`
});

this.on = this.webhooks.on;
Expand All @@ -41,18 +41,32 @@ export class GitHubEventHandler {
}

transformEvent(event: EmitterWebhookEvent) {
let installationId: number | undefined = undefined;
if ("installation" in event.payload) {
installationId = event.payload.installation?.id;
console.log(this);
if ("installation" in event.payload && event.payload.installation?.id !== undefined) {
const octokit = this.getAuthenticatedOctokit(event.payload.installation.id);
return new GitHubContext(this, event, octokit);
} else {
const octokit = this.getUnauthenticatedOctokit();
return new GitHubContext(this, event, octokit);
}
const octokit = new customOctokit({
}

getAuthenticatedOctokit(installationId: number) {
return new customOctokit({
auth: {
appId: this._appId,
privateKey: this._privateKey,
installationId: installationId,
},
});
}

return new GitHubContext(event, octokit);
getUnauthenticatedOctokit() {
return new customOctokit({
auth: {
appId: this._appId,
privateKey: this._privateKey,
},
});
}
}
70 changes: 41 additions & 29 deletions src/github/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,8 @@ import { EmitterWebhookEvent } from "@octokit/webhooks";
import { GitHubEventHandler } from "../github-event-handler";
import { getConfig } from "../utils/config";
import { issueCommentCreated } from "./issue-comment/created";

export function bindHandlers(webhooks: GitHubEventHandler) {
webhooks.on("issue_comment.created", issueCommentCreated);
webhooks.onAny(
tryCatchWrapper(async (event) => {
const context = webhooks.transformEvent(event);

const config = await getConfig(context);

if (!config) {
console.log("No config found");
return;
}

const handler = config.handlers.events[event.name];

if (handler.length === 0) {
console.log(`No handler found for event ${event.name}`);
return;
}

for (const { workflow } of handler) {
console.log(`Calling handler for event ${event.name} and workflow ${workflow}`);

// TODO: call the workflow
}
})
);
}
import { repositoryDispatch } from "./repository-dispatch";
import { dispatchWorkflow } from "../utils/workflow-dispatch";

function tryCatchWrapper(fn: (event: EmitterWebhookEvent) => unknown) {
return async (event: EmitterWebhookEvent) => {
Expand All @@ -41,3 +14,42 @@ function tryCatchWrapper(fn: (event: EmitterWebhookEvent) => unknown) {
}
};
}

export function bindHandlers(eventHandler: GitHubEventHandler) {
eventHandler.on("issue_comment.created", issueCommentCreated);
eventHandler.on("repository_dispatch", repositoryDispatch);
eventHandler.onAny(tryCatchWrapper((event) => handleEvent(event, eventHandler)));
}

async function handleEvent(event: EmitterWebhookEvent, eventHandler: InstanceType<typeof GitHubEventHandler>) {
const context = eventHandler.transformEvent(event);

const config = await getConfig(context);

if (!config) {
console.log("No config found");
return;
}

const handler = config.handlers.events[context.key];

if (handler.length === 0) {
console.log(`No handler found for event ${event.name}`);
return;
}

for (const { workflow, settings } of handler) {
console.log(`Calling handler for event ${event.name} and workflow ${workflow}`);

await dispatchWorkflow(context, {
owner: workflow.owner,
repository: workflow.repository,
workflowId: workflow.workflowId,
ref: workflow.branch,
inputs: {
event: JSON.stringify(event),
settings: JSON.stringify(settings),
},
});
}
}
5 changes: 5 additions & 0 deletions src/github/handlers/repository-dispatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { GitHubContext } from "../github-context";

export async function repositoryDispatch(event: GitHubContext<"repository_dispatch">) {
console.log("Repository dispatch event received", event.payload);
}
20 changes: 14 additions & 6 deletions src/github/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,25 @@ enum Commands {

const handlerSchema = T.Array(
T.Object({
workflow: T.String(),
settings: T.Unknown(),
workflow: T.Object({
owner: T.String(),
repository: T.String(),
workflowId: T.String(),
branch: T.Optional(T.String()),
}),
settings: T.Optional(T.Unknown()),
}),
{ default: [] }
);

export const configSchema = T.Object({
handlers: T.Object({
commands: T.Record(T.Enum(Commands), handlerSchema, { default: {} }),
events: T.Record(T.Enum(GitHubEvent), handlerSchema, { default: {} }),
}),
handlers: T.Object(
{
commands: T.Record(T.Enum(Commands), handlerSchema, { default: {} }),
events: T.Record(T.Enum(GitHubEvent), handlerSchema, { default: {} }),
},
{ default: {} }
),
});

export type Config = StaticDecode<typeof configSchema>;
2 changes: 1 addition & 1 deletion src/github/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function getConfig(context: GitHubContext): Promise<Config | null>
);

try {
return Value.Decode(configSchema, _repoConfig);
return Value.Decode(configSchema, Value.Default(configSchema, _repoConfig));
} catch (error) {
console.error("Error decoding config", error);
return null;
Expand Down
41 changes: 41 additions & 0 deletions src/github/utils/workflow-dispatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { customOctokit } from "../github-client";
import { GitHubContext } from "../github-context";

interface WorkflowDispatchOptions {
owner: string;
repository: string;
workflowId: string;
ref?: string;
inputs?: { [key: string]: string };
}

async function getInstallationOctokitForOrg(context: GitHubContext, owner: string): Promise<InstanceType<typeof customOctokit>> {
const installations = await context.octokit.apps.listInstallations();
const installation = installations.data.find((inst) => inst.account?.login === owner);

if (!installation) {
throw new Error(`No installation found for owner: ${owner}`);
}

return context.eventHandler.getAuthenticatedOctokit(installation.id);
}

export async function dispatchWorkflow(context: GitHubContext, options: WorkflowDispatchOptions) {
const authenticatedOctokit = await getInstallationOctokitForOrg(context, options.owner);

return await authenticatedOctokit.actions.createWorkflowDispatch({
owner: options.owner,
repo: options.repository,
workflow_id: options.workflowId,
ref: options.ref ?? (await getDefaultBranch(context, options.owner, options.repository)),
inputs: options.inputs,
});
}

async function getDefaultBranch(context: GitHubContext, owner: string, repository: string) {
const repo = await context.octokit.repos.get({
owner: owner,
repo: repository,
});
return repo.data.default_branch;
}

0 comments on commit b17af14

Please sign in to comment.