Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/workflow-processor/src/config-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class ConfigError extends Error {
constructor(message: string) {
super(message);
}
}

export default ConfigError;
152 changes: 41 additions & 111 deletions apps/workflow-processor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,30 @@ import { EventBridgeHandler } from "aws-lambda";
import { PushEvent } from "@octokit/webhooks-types";
import { workflowLoggingService } from "@evergreendocs/services";

import { createCompletion } from "./services/open-ai-service.js";
import presetFactory from "./presets/preset-factory.js";
import GithubRepositoryService from "./services/github-repository-service.js";
import GithubRepo from "./services/github-repo.js";
import EvergreenConfig from "./schema/evergreen-config.js";
import ConfigError from "./config-error.js";
import Task from "./task.js";

const handler: EventBridgeHandler<"push", PushEvent, boolean> = async (event) => {
const body = event?.detail;
const gitEvent = event?.detail;

if (!body?.commits?.length) {
if (!gitEvent?.commits?.length) {
return true;
}

console.log("Received event", {
repository: body.repository?.full_name,
ref: body.ref,
commits: body.commits.map((commit) => commit.id),
repository: gitEvent.repository?.full_name,
ref: gitEvent.ref,
commits: gitEvent.commits.map((commit) => commit.id),
});

const repoOwner = body.repository?.owner?.login;
const repoName = body.repository?.name;
const installationId = body.installation?.id;
const headCommit = body.head_commit?.id;
const repositoryFullName = body.repository?.full_name;
const commitBranch = body.ref.replace("refs/heads/", "");
const repoOwner = gitEvent.repository?.owner?.login;
const repoName = gitEvent.repository?.name;
const installationId = gitEvent.installation?.id;
const headCommit = gitEvent.head_commit?.id;
const repositoryFullName = gitEvent.repository?.full_name;
const commitBranch = gitEvent.ref.replace("refs/heads/", "");

if (!repoOwner || !repoName || !installationId || !headCommit) {
return false;
Expand All @@ -34,22 +34,15 @@ const handler: EventBridgeHandler<"push", PushEvent, boolean> = async (event) =>
await workflowLoggingService.entities.workflow
.create({
headCommit,
headCommitMessage: body.head_commit?.message || "Unknown commit message",
headCommitMessage: gitEvent.head_commit?.message || "Unknown commit message",
repositoryFullName,
status: "in_progress",
})
.go();

const githubRepositoryService = new GithubRepositoryService({
repoOwner,
repoName,
installationId,
});
const githubRepo = new GithubRepo({ repoOwner, repoName, installationId });

const [config] = await githubRepositoryService.fetchFiles(
["evergreen.config.json"],
commitBranch
);
const [config] = await githubRepo.fetchFiles(["evergreen.config.json"], commitBranch);

if (!config) {
await workflowLoggingService.entities.workflow
Expand All @@ -74,96 +67,33 @@ const handler: EventBridgeHandler<"push", PushEvent, boolean> = async (event) =>
}

await Promise.all(
parsedConfig.generates.map(async (generate, presetIndex) => {
try {
const preset = presetFactory(generate, body, githubRepositoryService);
parsedConfig.generates.map(async (presetConfig, presetIndex) => {
const task = new Task(presetConfig, presetIndex, gitEvent, githubRepo);

await workflowLoggingService.entities.task
.create({
headCommit,
preset: generate.preset,
index: presetIndex,
status: "in_progress",
repositoryFullName,
})
.go();

const hasUpdates = await preset.hasUpdates();

if (!hasUpdates) {
console.log("No updates for preset", {
preset: generate.preset,
repository: body.repository?.full_name,
ref: body.ref,
commits: body.commits.map((commit) => commit.id),
commitBranch,
try {
await task.run();
} catch (error) {
let reason = "Unknown error";

if (error instanceof Error) {
console.error("Failed to update preset", {
error,
preset: presetConfig.preset,
repository: gitEvent.repository?.full_name,
ref: gitEvent.ref,
commits: gitEvent.commits.map((commit) => commit.id),
});

await workflowLoggingService.entities.task
.patch({ headCommit, preset: generate.preset, index: presetIndex })
.set({ status: "skipped", reason: "No updates" })
.go();

return;
if (error instanceof ConfigError) {
reason = error.message;
} else {
reason = "Internal error";
}
}

console.log("Found updates for preset", {
preset: generate.preset,
repository: body.repository?.full_name,
ref: body.ref,
commits: body.commits.map((commit) => commit.id),
});

await preset.fetchFiles();
const prompt = preset.createPrompt();

const output = await createCompletion(prompt);

// TODO: Make this smarter/configurable
await githubRepositoryService.createBranch({ branchName: preset.branchName });
const commit = await githubRepositoryService.commitFile({
branchName: preset.branchName,
path: "path" in generate ? generate.path : generate.outputPath,
content: output,
message: preset.branchName,
});
// TODO: Make this smarter/configurable
const pullRequest = await githubRepositoryService.createPullRequest({
branchName: preset.branchName,
title: preset.branchName,
});

await workflowLoggingService.entities.task
.patch({ headCommit, preset: generate.preset, index: presetIndex })
.set({
status: "success",
outputPullRequestUrl: pullRequest.html_url,
outputCommit: commit.commit.sha || "Unknown commit sha",
outputCommitMessage: commit.commit.message || "Unknown commit message",
})
.go();

console.log("Updated preset", {
preset: generate.preset,
repository: body.repository?.full_name,
ref: body.ref,
commits: body.commits.map((commit) => commit.id),
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Internal error";

console.error("Failed to update preset", {
error: errorMessage,
preset: generate.preset,
repository: body.repository?.full_name,
ref: body.ref,
commits: body.commits.map((commit) => commit.id),
});

await workflowLoggingService.entities.task
.patch({ headCommit, preset: generate.preset, index: presetIndex })
// TODO: make this not give away internal errors to the user
.set({ status: "failed", reason: errorMessage })
.patch({ headCommit, preset: presetConfig.preset, index: presetIndex })
.set({ status: "failed", reason })
.go();
}
})
Expand All @@ -175,9 +105,9 @@ const handler: EventBridgeHandler<"push", PushEvent, boolean> = async (event) =>
.go();

console.log("Processed event", {
repository: body.repository?.full_name,
ref: body.ref,
commits: body.commits.map((commit) => commit.id),
repository: gitEvent.repository?.full_name,
ref: gitEvent.ref,
commits: gitEvent.commits.map((commit) => commit.id),
});

return true;
Expand Down
10 changes: 3 additions & 7 deletions apps/workflow-processor/src/presets/base.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import { PushEvent } from "@octokit/webhooks-types";

import { PresetConfig } from "../schema/evergreen-config.js";
import GithubRepositoryService from "../services/github-repository-service.js";
import GithubRepo from "../services/github-repo.js";
import type { WorkflowProcessorFile } from "../types/index.js";

abstract class BasePreset<T = PresetConfig> {
protected presetConfig: T;
protected pushEvent: PushEvent;
protected _branchName?: string;
protected files?: WorkflowProcessorFile[];
protected githubRepositoryService: GithubRepositoryService;
protected githubRepositoryService: GithubRepo;

constructor(
presetConfig: T,
pushEvent: PushEvent,
githubRepositoryService: GithubRepositoryService
) {
constructor(presetConfig: T, pushEvent: PushEvent, githubRepositoryService: GithubRepo) {
this.presetConfig = presetConfig;
this.pushEvent = pushEvent;
this.githubRepositoryService = githubRepositoryService;
Expand Down
9 changes: 5 additions & 4 deletions apps/workflow-processor/src/presets/code-comment.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CodeCommentPresetConfig } from "../schema/presets/index.js";
import ConfigError from "../config-error.js";

import { BasePreset } from "./base.js";

Expand All @@ -24,7 +25,7 @@ class CodeCommentPreset extends BasePreset<CodeCommentPresetConfig> {
);

if (!this.files?.length) {
throw new Error(`No files found for ${this.presetConfig.path}`);
throw new ConfigError(`No files found for .path value [${this.presetConfig.path}]`);
}

return this.files;
Expand All @@ -34,7 +35,7 @@ class CodeCommentPreset extends BasePreset<CodeCommentPresetConfig> {
const file = this.files?.[0];

if (!file) {
throw new Error(`No files found for ${this.presetConfig.path}`);
throw new ConfigError(`No files found for .path value [${this.presetConfig.path}]`);
}

const { content } = file;
Expand All @@ -49,12 +50,12 @@ class CodeCommentPreset extends BasePreset<CodeCommentPresetConfig> {
}
}

throw new Error(`No function found for ${this.presetConfig.path}`);
throw new ConfigError(`No function found for .path value [${this.presetConfig.name}]`);
}

createPrompt(): string {
if (!this.files?.[0]) {
throw new Error(`No files found for ${this.presetConfig.path}`);
throw new ConfigError(`No files found for .path value [${this.presetConfig.path}]`);
}

return this.files[0].content;
Expand Down
23 changes: 12 additions & 11 deletions apps/workflow-processor/src/presets/preset-factory.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
import { PushEvent } from "@octokit/webhooks-types";

import GithubRepositoryService from "../services/github-repository-service.js";
import type { EvergreenConfig } from "../schema/evergreen-config.js";
import type { PresetConfig } from "../schema/evergreen-config.js";
import GithubRepo from "../services/github-repo.js";
import ConfigError from "../config-error.js";

import CodeCommentPreset from "./code-comment.js";
import { BasePreset } from "./base.js";
import ReadmePreset from "./readme.js";
import TranslatePreset from "./translate.js";
import CodeCommentPreset from "./code-comment.js";

const unknownPresetError = (input: never) =>
new Error(`Unknown preset: ${JSON.stringify(input, null, 2)}`);
new ConfigError(`Unknown preset: ${JSON.stringify(input, null, 2)}`);

function presetFactory(
generates: EvergreenConfig["generates"][number],
presetConfig: PresetConfig,
githubEvent: PushEvent,
githubRepositoryService: GithubRepositoryService
githubRepositoryService: GithubRepo
): BasePreset {
switch (generates.preset) {
switch (presetConfig.preset) {
case "readme":
return new ReadmePreset(generates, githubEvent, githubRepositoryService);
return new ReadmePreset(presetConfig, githubEvent, githubRepositoryService);

case "translate":
return new TranslatePreset(generates, githubEvent, githubRepositoryService);
return new TranslatePreset(presetConfig, githubEvent, githubRepositoryService);

case "code-comment":
return new CodeCommentPreset(generates, githubEvent, githubRepositoryService);
return new CodeCommentPreset(presetConfig, githubEvent, githubRepositoryService);

default:
throw unknownPresetError(generates);
throw unknownPresetError(presetConfig);
}
}

Expand Down
3 changes: 2 additions & 1 deletion apps/workflow-processor/src/presets/readme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import minimatch from "minimatch";

import { ReadmePresetConfig } from "../schema/presets/index.js";
import { JSONSchemaForNPMPackageJsonFiles } from "../types/index.js";
import ConfigError from "../config-error.js";

import { BasePreset } from "./base.js";

Expand Down Expand Up @@ -183,7 +184,7 @@ class ReadmePreset extends BasePreset<ReadmePresetConfig> {
const rootPackageJsonFile = this.files.find((file) => file.path === "package.json");

if (!rootPackageJsonFile?.content) {
throw new Error("package.json not found");
throw new ConfigError("package.json not found");
}
const rootPackageJson: JSONSchemaForNPMPackageJsonFiles = JSON.parse(
rootPackageJsonFile.content
Expand Down
5 changes: 4 additions & 1 deletion apps/workflow-processor/src/presets/translate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TranslatePresetConfig } from "../schema/presets/index.js";
import ConfigError from "../config-error.js";

import { BasePreset } from "./base.js";

Expand All @@ -21,7 +22,9 @@ class TranslatePreset extends BasePreset<TranslatePresetConfig> {

createPrompt(): string {
if (!this.files?.length) {
throw new Error("Files not fetched");
throw new ConfigError(
`Files not fetched for .inputPath value [${this.presetConfig.inputPath}]`
);
}

const fileName =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import { operations as Operations } from "@octokit/openapi-types";
import { WorkflowProcessorFile } from "../types/index.js";
import config from "../config.js";

type GithubRepositoryServiceOptions = {
type GithubRepoOptions = {
installationId: number;
repoOwner: string;
repoName: string;
};

class GithubRepositoryService extends Octokit {
class GithubRepo extends Octokit {
readonly repoOwner: string;
readonly repoName: string;

constructor({ installationId, repoOwner, repoName }: GithubRepositoryServiceOptions) {
constructor({ installationId, repoOwner, repoName }: GithubRepoOptions) {
super({
authStrategy: createAppAuth,
auth: {
Expand Down Expand Up @@ -222,4 +222,4 @@ class GithubRepositoryService extends Octokit {
}
}

export default GithubRepositoryService;
export default GithubRepo;
Loading