mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 08:42:13 +02:00
Compare commits
1 Commits
postmortem
...
vs-code-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c18aebde8 |
@@ -1,56 +0,0 @@
|
||||
.git
|
||||
|
||||
node_modules
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
node_modules
|
||||
|
||||
.idea
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
env.js
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
yarn.lock
|
||||
Untitled-1
|
||||
*.local.sh
|
||||
*.local.yaml
|
||||
run
|
||||
stop
|
||||
|
||||
nohup.out*
|
||||
|
||||
encrypted-credentials.tar
|
||||
encrypted-credentials/
|
||||
|
||||
_README.md
|
||||
|
||||
# Important Add production values to gitignore.
|
||||
values-saas-production.yaml
|
||||
kubernetes/values-saas-production.yaml
|
||||
|
||||
/private
|
||||
|
||||
/tls_cert.pem
|
||||
/tls_key.pem
|
||||
/keys
|
||||
|
||||
temp_readme.md
|
||||
|
||||
tests/coverage
|
||||
|
||||
settings.json
|
||||
|
||||
GoSDK/tester/
|
||||
@@ -1,6 +0,0 @@
|
||||
ONEUPTIME_URL=https://oneuptime.com
|
||||
ONEUPTIME_REPOSITORY_SECRET_KEY=your-repository-secret-key
|
||||
CODE_REPOSITORY_PASSWORD=
|
||||
CODE_REPOSITORY_USERNAME=
|
||||
# Optional. If this is left blank then this url will be ONEUPTIME_URL/llama
|
||||
ONEUPTIME_LLM_SERVER_URL=
|
||||
1
Copilot/.gitattributes
vendored
1
Copilot/.gitattributes
vendored
@@ -1 +0,0 @@
|
||||
*.js text eol=lf
|
||||
16
Copilot/.gitignore
vendored
16
Copilot/.gitignore
vendored
@@ -1,16 +0,0 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
#/backend/node_modules
|
||||
/kubernetes
|
||||
/node_modules
|
||||
.idea
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
yarn.lock
|
||||
@@ -1,76 +0,0 @@
|
||||
import URL from "Common/Types/API/URL";
|
||||
import LlmType from "./Types/LlmType";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
|
||||
type GetStringFunction = () => string;
|
||||
type GetStringOrNullFunction = () => string | null;
|
||||
type GetURLFunction = () => URL;
|
||||
|
||||
export const MIN_ITEMS_IN_QUEUE_PER_SERVICE_CATALOG: number = 10;
|
||||
|
||||
export const GetIsCopilotDisabled: () => boolean = () => {
|
||||
return process.env["DISABLE_COPILOT"] === "true";
|
||||
};
|
||||
|
||||
export const GetOneUptimeURL: GetURLFunction = () => {
|
||||
return URL.fromString(
|
||||
process.env["ONEUPTIME_URL"] || "https://oneuptime.com",
|
||||
);
|
||||
};
|
||||
|
||||
export const GetRepositorySecretKey: GetStringOrNullFunction = ():
|
||||
| string
|
||||
| null => {
|
||||
return process.env["ONEUPTIME_REPOSITORY_SECRET_KEY"] || null;
|
||||
};
|
||||
|
||||
export const GetLocalRepositoryPath: GetStringFunction = (): string => {
|
||||
return "/repository";
|
||||
};
|
||||
|
||||
export const GetCodeRepositoryPassword: GetStringOrNullFunction = ():
|
||||
| string
|
||||
| null => {
|
||||
const token: string | null = process.env["CODE_REPOSITORY_PASSWORD"] || null;
|
||||
return token;
|
||||
};
|
||||
|
||||
export const GetCodeRepositoryUsername: GetStringOrNullFunction = ():
|
||||
| string
|
||||
| null => {
|
||||
const username: string | null =
|
||||
process.env["CODE_REPOSITORY_USERNAME"] || null;
|
||||
return username;
|
||||
};
|
||||
|
||||
export const GetLlmServerUrl: GetURLFunction = () => {
|
||||
if (!process.env["ONEUPTIME_LLM_SERVER_URL"]) {
|
||||
throw new BadDataException("ONEUPTIME_LLM_SERVER_URL is not set");
|
||||
}
|
||||
|
||||
return URL.fromString(process.env["ONEUPTIME_LLM_SERVER_URL"]);
|
||||
};
|
||||
|
||||
export const GetOpenAIAPIKey: GetStringOrNullFunction = (): string | null => {
|
||||
return process.env["OPENAI_API_KEY"] || null;
|
||||
};
|
||||
|
||||
export const GetOpenAIModel: GetStringOrNullFunction = (): string | null => {
|
||||
return process.env["OPENAI_MODEL"] || "gpt-4o";
|
||||
};
|
||||
|
||||
type GetLlmTypeFunction = () => LlmType;
|
||||
|
||||
export const GetLlmType: GetLlmTypeFunction = (): LlmType => {
|
||||
if (GetOpenAIAPIKey() && GetOpenAIModel()) {
|
||||
return LlmType.OpenAI;
|
||||
}
|
||||
|
||||
if (GetLlmServerUrl()) {
|
||||
return LlmType.ONEUPTIME_LLM;
|
||||
}
|
||||
|
||||
return LlmType.ONEUPTIME_LLM;
|
||||
};
|
||||
|
||||
export const FixNumberOfCodeEventsInEachRun: number = 5;
|
||||
@@ -1,80 +0,0 @@
|
||||
#
|
||||
# OneUptime-copilot Dockerfile
|
||||
#
|
||||
|
||||
# Pull base image nodejs image.
|
||||
FROM public.ecr.aws/docker/library/node:22.3.0
|
||||
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
|
||||
|
||||
RUN npm config set fetch-retries 5
|
||||
RUN npm config set fetch-retry-mintimeout 100000
|
||||
RUN npm config set fetch-retry-maxtimeout 600000
|
||||
|
||||
|
||||
ARG GIT_SHA
|
||||
ARG APP_VERSION
|
||||
|
||||
ENV GIT_SHA=${GIT_SHA}
|
||||
ENV APP_VERSION=${APP_VERSION}
|
||||
|
||||
|
||||
# IF APP_VERSION is not set, set it to 1.0.0
|
||||
RUN if [ -z "$APP_VERSION" ]; then export APP_VERSION=1.0.0; fi
|
||||
|
||||
|
||||
# Install bash.
|
||||
RUN apt-get install bash -y && apt-get install curl -y
|
||||
|
||||
# Install python
|
||||
RUN apt-get update && apt-get install -y .gyp python3 make g++
|
||||
|
||||
#Use bash shell by default
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
|
||||
RUN mkdir -p /usr/src
|
||||
|
||||
WORKDIR /usr/src/Common
|
||||
COPY ./Common/package*.json /usr/src/Common/
|
||||
# Set version in ./Common/package.json to the APP_VERSION
|
||||
RUN sed -i "s/\"version\": \".*\"/\"version\": \"$APP_VERSION\"/g" /usr/src/Common/package.json
|
||||
RUN npm install
|
||||
COPY ./Common /usr/src/Common
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
ENV PRODUCTION=true
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY ./Copilot/package*.json /usr/src/app/
|
||||
RUN npm install
|
||||
|
||||
|
||||
# Create /repository/ directory where the app will store the repository
|
||||
RUN mkdir -p /repository
|
||||
|
||||
# Set the stack trace limit to 0 to show full stack traces
|
||||
ENV NODE_OPTIONS='--stack-trace-limit=30'
|
||||
|
||||
{{ if eq .Env.ENVIRONMENT "development" }}
|
||||
#Run the app
|
||||
CMD [ "npm", "run", "dev" ]
|
||||
{{ else }}
|
||||
# Copy app source
|
||||
COPY ./Copilot /usr/src/app
|
||||
# Bundle app source
|
||||
RUN npm run compile
|
||||
# Set permission to write logs and cache in case container run as non root
|
||||
RUN chown -R 1000:1000 "/tmp/npm" && chmod -R 2777 "/tmp/npm"
|
||||
#Run the app
|
||||
CMD [ "npm", "start" ]
|
||||
{{ end }}
|
||||
@@ -1,8 +0,0 @@
|
||||
import Exception from "Common/Types/Exception/Exception";
|
||||
import ExceptionCode from "Common/Types/Exception/ExceptionCode";
|
||||
|
||||
export default class CopilotActionException extends Exception {
|
||||
public constructor(code: ExceptionCode, message: string) {
|
||||
super(code, message);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import Exception from "Common/Types/Exception/Exception";
|
||||
import ExceptionCode from "Common/Types/Exception/ExceptionCode";
|
||||
|
||||
export default class CopilotActionProcessingException extends Exception {
|
||||
public constructor(code: ExceptionCode, message: string) {
|
||||
super(code, message);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import ExceptionCode from "Common/Types/Exception/ExceptionCode";
|
||||
import CopilotActionProcessingException from "./CopilotActionProcessingException";
|
||||
|
||||
export default class ErrorGettingResponseFromLLM extends CopilotActionProcessingException {
|
||||
public constructor(message: string) {
|
||||
super(ExceptionCode.BadDataException, message);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import ExceptionCode from "Common/Types/Exception/ExceptionCode";
|
||||
import CopilotActionProcessingException from "./CopilotActionProcessingException";
|
||||
|
||||
export default class LLMTimeoutException extends CopilotActionProcessingException {
|
||||
public constructor(message: string) {
|
||||
super(ExceptionCode.BadDataException, message);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import ExceptionCode from "Common/Types/Exception/ExceptionCode";
|
||||
import CopilotActionProcessingException from "./CopilotActionProcessingException";
|
||||
|
||||
export default class NotAcceptedFileExtentionForCopilotAction extends CopilotActionProcessingException {
|
||||
public constructor(message: string) {
|
||||
super(ExceptionCode.BadDataException, message);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import CodeRepositoryUtil from "./Utils/CodeRepository";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import dotenv from "dotenv";
|
||||
import Init from "./Init";
|
||||
import Telemetry from "Common/Server/Utils/Telemetry";
|
||||
|
||||
const APP_NAME: string = "copilot";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
logger.info("OneUptime Copilot is starting...");
|
||||
|
||||
// Initialize telemetry
|
||||
Telemetry.init({
|
||||
serviceName: APP_NAME,
|
||||
});
|
||||
|
||||
Init()
|
||||
.then(() => {
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(async (error: Error | HTTPErrorResponse) => {
|
||||
try {
|
||||
logger.error(error);
|
||||
await CodeRepositoryUtil.discardChanges();
|
||||
|
||||
// change back to main branch.
|
||||
await CodeRepositoryUtil.checkoutMainBranch();
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
// do nothing.
|
||||
}
|
||||
|
||||
logger.error("Error in starting OneUptime Copilot: ");
|
||||
|
||||
if (error instanceof HTTPErrorResponse) {
|
||||
logger.error(error.message);
|
||||
} else if (error instanceof Error) {
|
||||
logger.error(error.message);
|
||||
} else {
|
||||
logger.error(error);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
231
Copilot/Init.ts
231
Copilot/Init.ts
@@ -1,231 +0,0 @@
|
||||
import CodeRepositoryUtil, {
|
||||
CodeRepositoryResult,
|
||||
RepoScriptType,
|
||||
} from "./Utils/CodeRepository";
|
||||
import InitUtil from "./Utils/Init";
|
||||
import ServiceRepositoryUtil from "./Utils/ServiceRepository";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import CopilotActionUtil from "./Utils/CopilotAction";
|
||||
import CopilotAction from "Common/Models/DatabaseModels/CopilotAction";
|
||||
import {
|
||||
FixNumberOfCodeEventsInEachRun,
|
||||
GetIsCopilotDisabled,
|
||||
GetLlmType,
|
||||
} from "./Config";
|
||||
import CopilotActionService, {
|
||||
CopilotExecutionResult,
|
||||
} from "./Service/CopilotActions/Index";
|
||||
import CopilotActionStatus from "Common/Types/Copilot/CopilotActionStatus";
|
||||
import PullRequest from "Common/Types/CodeRepository/PullRequest";
|
||||
import ServiceCopilotCodeRepository from "Common/Models/DatabaseModels/ServiceCopilotCodeRepository";
|
||||
import CopilotActionProcessingException from "./Exceptions/CopilotActionProcessingException";
|
||||
import CopilotPullRequest from "Common/Models/DatabaseModels/CopilotPullRequest";
|
||||
import ProcessUtil from "./Utils/Process";
|
||||
|
||||
let currentFixCount: number = 1;
|
||||
|
||||
const init: PromiseVoidFunction = async (): Promise<void> => {
|
||||
// check if copilot is disabled.
|
||||
|
||||
if (GetIsCopilotDisabled()) {
|
||||
logger.info("Copilot is disabled. Exiting.");
|
||||
ProcessUtil.haltProcessWithSuccess();
|
||||
}
|
||||
|
||||
logger.info(`Using ${GetLlmType()} as the AI model.`);
|
||||
|
||||
await CodeRepositoryUtil.setAuthorIdentity({
|
||||
email: "copilot@oneuptime.com",
|
||||
name: "OneUptime Copilot",
|
||||
});
|
||||
|
||||
const codeRepositoryResult: CodeRepositoryResult = await InitUtil.init();
|
||||
|
||||
// before cloning the repo, check if there are any services to improve.
|
||||
ServiceRepositoryUtil.setCodeRepositoryResult({
|
||||
codeRepositoryResult,
|
||||
});
|
||||
|
||||
const servicesToImprove: ServiceCopilotCodeRepository[] =
|
||||
await ServiceRepositoryUtil.getServicesToImprove();
|
||||
|
||||
logger.debug(`Found ${servicesToImprove.length} services to improve.`);
|
||||
|
||||
// if no services to improve, then exit.
|
||||
if (servicesToImprove.length === 0) {
|
||||
logger.info("No services to improve. Exiting.");
|
||||
ProcessUtil.haltProcessWithSuccess();
|
||||
}
|
||||
|
||||
for (const serviceToImprove of servicesToImprove) {
|
||||
logger.debug(`- ${serviceToImprove.serviceCatalog!.name}`);
|
||||
}
|
||||
|
||||
await cloneRepository({
|
||||
codeRepositoryResult,
|
||||
});
|
||||
|
||||
await setUpRepository();
|
||||
|
||||
for (const serviceRepository of servicesToImprove) {
|
||||
checkIfCurrentFixCountIsLessThanFixNumberOfCodeEventsInEachRun();
|
||||
|
||||
const actionsToWorkOn: Array<CopilotAction> =
|
||||
await CopilotActionUtil.getActionsToWorkOn({
|
||||
serviceCatalogId: serviceRepository.serviceCatalog!.id!,
|
||||
serviceRepositoryId: serviceRepository.id!,
|
||||
});
|
||||
|
||||
for (const actionToWorkOn of actionsToWorkOn) {
|
||||
checkIfCurrentFixCountIsLessThanFixNumberOfCodeEventsInEachRun();
|
||||
// check copilot events for this file.
|
||||
|
||||
let executionResult: CopilotExecutionResult | null = null;
|
||||
|
||||
let currentRetryCount: number = 0;
|
||||
const maxRetryCount: number = 3;
|
||||
|
||||
while (currentRetryCount < maxRetryCount) {
|
||||
try {
|
||||
executionResult = await executeAction({
|
||||
serviceRepository,
|
||||
copilotAction: actionToWorkOn,
|
||||
});
|
||||
break;
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
currentRetryCount++;
|
||||
await CodeRepositoryUtil.discardAllChangesOnCurrentBranch();
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
executionResult &&
|
||||
executionResult.status === CopilotActionStatus.PR_CREATED
|
||||
) {
|
||||
currentFixCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
interface ExecuteActionData {
|
||||
serviceRepository: ServiceCopilotCodeRepository;
|
||||
copilotAction: CopilotAction;
|
||||
}
|
||||
|
||||
type ExecutionActionFunction = (
|
||||
data: ExecuteActionData,
|
||||
) => Promise<CopilotExecutionResult | null>;
|
||||
|
||||
const executeAction: ExecutionActionFunction = async (
|
||||
data: ExecuteActionData,
|
||||
): Promise<CopilotExecutionResult | null> => {
|
||||
const { serviceRepository, copilotAction } = data;
|
||||
|
||||
try {
|
||||
return await CopilotActionService.executeAction({
|
||||
serviceRepository: serviceRepository,
|
||||
copilotAction: copilotAction,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof CopilotActionProcessingException) {
|
||||
// This is not a serious exception, so we just move on to the next action.
|
||||
logger.info(e.message);
|
||||
return null;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
type CloneRepositoryFunction = (data: {
|
||||
codeRepositoryResult: CodeRepositoryResult;
|
||||
}) => Promise<void>;
|
||||
|
||||
const cloneRepository: CloneRepositoryFunction = async (data: {
|
||||
codeRepositoryResult: CodeRepositoryResult;
|
||||
}): Promise<void> => {
|
||||
const { codeRepositoryResult } = data;
|
||||
|
||||
logger.info(
|
||||
`Cloning the repository ${codeRepositoryResult.codeRepository.name} to a temporary directory.`,
|
||||
);
|
||||
|
||||
// now clone this repository to a temporary directory - /repository
|
||||
await CodeRepositoryUtil.cloneRepository({
|
||||
codeRepository: codeRepositoryResult.codeRepository,
|
||||
});
|
||||
|
||||
// Check if OneUptime Copilot has setup properly.
|
||||
|
||||
const onAfterCloneScript: string | null =
|
||||
await CodeRepositoryUtil.getRepoScript({
|
||||
scriptType: RepoScriptType.OnAfterClone,
|
||||
});
|
||||
|
||||
if (!onAfterCloneScript) {
|
||||
logger.debug("No on-after-clone script found for this repository.");
|
||||
}
|
||||
|
||||
if (onAfterCloneScript) {
|
||||
logger.info("Executing on-after-clone script.");
|
||||
await CodeRepositoryUtil.executeScript({
|
||||
script: onAfterCloneScript,
|
||||
});
|
||||
logger.info("on-after-clone script executed successfully.");
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Repository ${codeRepositoryResult.codeRepository.name} cloned successfully.`,
|
||||
);
|
||||
};
|
||||
|
||||
const checkIfCurrentFixCountIsLessThanFixNumberOfCodeEventsInEachRun: VoidFunction =
|
||||
(): void => {
|
||||
if (currentFixCount <= FixNumberOfCodeEventsInEachRun) {
|
||||
return;
|
||||
}
|
||||
logger.info(
|
||||
`Copilot has fixed ${FixNumberOfCodeEventsInEachRun} code events. Thank you for using Copilot. If you wish to fix more code events, please run Copilot again.`,
|
||||
);
|
||||
|
||||
ProcessUtil.haltProcessWithSuccess();
|
||||
};
|
||||
|
||||
const setUpRepository: PromiseVoidFunction = async (): Promise<void> => {
|
||||
const isSetupProperly: boolean =
|
||||
await CodeRepositoryUtil.isRepoSetupProperly();
|
||||
|
||||
if (isSetupProperly) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the repo is not set up properly, then check if there's an outstanding setup Pr for this repo.
|
||||
logger.info("Setting up the repository.");
|
||||
|
||||
// check if there's an outstanding setup PR for this repo.
|
||||
const setupPullRequest: CopilotPullRequest | null =
|
||||
await CodeRepositoryUtil.getOpenSetupPullRequest();
|
||||
|
||||
if (setupPullRequest) {
|
||||
logger.info(
|
||||
`There's an open setup PR for this repository: ${setupPullRequest.pullRequestId}. Please merge this PR to continue using Copilot. Exiting...`,
|
||||
);
|
||||
ProcessUtil.haltProcessWithSuccess();
|
||||
return;
|
||||
}
|
||||
|
||||
// if there's no setup PR, then create a new setup PR.
|
||||
const pullRequest: PullRequest = await CodeRepositoryUtil.setUpRepo();
|
||||
|
||||
logger.info(
|
||||
"Repository setup PR created - #" +
|
||||
pullRequest.pullRequestNumber +
|
||||
". Please megre this PR to continue using Copilot. Exiting..",
|
||||
);
|
||||
|
||||
ProcessUtil.haltProcessWithSuccess();
|
||||
};
|
||||
|
||||
export default init;
|
||||
@@ -1,6 +0,0 @@
|
||||
# OneUptime Copilot
|
||||
|
||||
Copilot is a tool that helps you improve your codebase automatically.
|
||||
|
||||
Please refer to the [official documentation](/Docs/Content/copilot) for more information.
|
||||
|
||||
@@ -1,423 +0,0 @@
|
||||
import CopilotActionType from "Common/Types/Copilot/CopilotActionType";
|
||||
import CopilotActionBase from "./CopilotActionsBase";
|
||||
import CodeRepositoryUtil from "../../Utils/CodeRepository";
|
||||
import TechStack from "Common/Types/ServiceCatalog/TechStack";
|
||||
import { CopilotPromptResult } from "../LLM/LLMBase";
|
||||
import Text from "Common/Types/Text";
|
||||
import { CopilotActionPrompt, CopilotProcess } from "./Types";
|
||||
import { PromptRole } from "../LLM/Prompt";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import FileActionProp from "Common/Types/Copilot/CopilotActionProps/FileActionProp";
|
||||
import CodeRepositoryFile from "Common/Server/Utils/CodeRepository/CodeRepositoryFile";
|
||||
import CopilotActionUtil from "../../Utils/CopilotAction";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import CopilotAction from "Common/Models/DatabaseModels/CopilotAction";
|
||||
import ServiceRepositoryUtil from "../../Utils/ServiceRepository";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import ArrayUtil from "Common/Utils/Array";
|
||||
import CopilotActionProp from "Common/Types/Copilot/CopilotActionProps/Index";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
|
||||
export default class AddSpans extends CopilotActionBase {
|
||||
public isRequirementsMet: boolean = false;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
this.copilotActionType = CopilotActionType.ADD_SPANS;
|
||||
this.acceptFileExtentions = CodeRepositoryUtil.getCodeFileExtentions();
|
||||
}
|
||||
|
||||
protected override async isActionRequired(data: {
|
||||
serviceCatalogId: ObjectID;
|
||||
serviceRepositoryId: ObjectID;
|
||||
copilotActionProp: FileActionProp;
|
||||
}): Promise<boolean> {
|
||||
// check if the action has already been processed for this file.
|
||||
const existingAction: CopilotAction | null =
|
||||
await CopilotActionUtil.getExistingAction({
|
||||
serviceCatalogId: data.serviceCatalogId,
|
||||
actionType: this.copilotActionType,
|
||||
actionProps: {
|
||||
filePath: data.copilotActionProp.filePath, // has this action run on this file before?
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingAction) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override async getActionPropsToQueue(data: {
|
||||
serviceCatalogId: ObjectID;
|
||||
serviceRepositoryId: ObjectID;
|
||||
maxActionsToQueue: number;
|
||||
}): Promise<Array<CopilotActionProp>> {
|
||||
// get files in the repo.
|
||||
logger.debug(
|
||||
`${this.copilotActionType} - Getting files to queue for improve comments.`,
|
||||
);
|
||||
|
||||
let totalActionsToQueue: number = 0;
|
||||
|
||||
logger.debug(`${this.copilotActionType} - Reading files in the service.`);
|
||||
|
||||
const files: Dictionary<CodeRepositoryFile> =
|
||||
await ServiceRepositoryUtil.getFilesByServiceCatalogId({
|
||||
serviceCatalogId: data.serviceCatalogId,
|
||||
});
|
||||
|
||||
logger.debug(
|
||||
`${this.copilotActionType} - Files read. ${Object.keys(files).length} files found.`,
|
||||
);
|
||||
|
||||
// get keys in random order.
|
||||
let fileKeys: string[] = Object.keys(files);
|
||||
|
||||
//randomize the order of the files.
|
||||
fileKeys = ArrayUtil.shuffle(fileKeys);
|
||||
|
||||
const actionsPropsQueued: Array<CopilotActionProp> = [];
|
||||
|
||||
for (const fileKey of fileKeys) {
|
||||
// check if the file is in accepted file extentions.
|
||||
const fileExtention: string = LocalFile.getFileExtension(
|
||||
files[fileKey]!.filePath,
|
||||
);
|
||||
|
||||
if (!this.acceptFileExtentions.includes(fileExtention)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const file: CodeRepositoryFile = files[fileKey]!;
|
||||
|
||||
logger.debug(
|
||||
`${this.copilotActionType} - Checking file: ${file.filePath}`,
|
||||
);
|
||||
|
||||
if (
|
||||
await this.isActionRequired({
|
||||
serviceCatalogId: data.serviceCatalogId,
|
||||
serviceRepositoryId: data.serviceRepositoryId,
|
||||
copilotActionProp: {
|
||||
filePath: file.filePath,
|
||||
},
|
||||
})
|
||||
) {
|
||||
actionsPropsQueued.push({
|
||||
filePath: file.filePath,
|
||||
});
|
||||
|
||||
totalActionsToQueue++;
|
||||
}
|
||||
|
||||
if (totalActionsToQueue >= data.maxActionsToQueue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return actionsPropsQueued;
|
||||
}
|
||||
|
||||
public override async getCommitMessage(
|
||||
data: CopilotProcess,
|
||||
): Promise<string> {
|
||||
return "Add Spans in " + (data.actionProp as FileActionProp).filePath;
|
||||
}
|
||||
|
||||
public override async getPullRequestTitle(
|
||||
data: CopilotProcess,
|
||||
): Promise<string> {
|
||||
return "Add spans in " + (data.actionProp as FileActionProp).filePath;
|
||||
}
|
||||
|
||||
public override async getPullRequestBody(
|
||||
data: CopilotProcess,
|
||||
): Promise<string> {
|
||||
return `Add spans in ${(data.actionProp as FileActionProp).filePath}
|
||||
|
||||
${await this.getDefaultPullRequestBody()}
|
||||
`;
|
||||
}
|
||||
|
||||
public override isActionComplete(_data: CopilotProcess): Promise<boolean> {
|
||||
return Promise.resolve(this.isRequirementsMet);
|
||||
}
|
||||
|
||||
public override async onExecutionStep(
|
||||
data: CopilotProcess,
|
||||
): Promise<CopilotProcess> {
|
||||
const filePath: string = (data.actionProp as FileActionProp).filePath;
|
||||
|
||||
if (!filePath) {
|
||||
throw new BadDataException("File Path is not set in the action prop.");
|
||||
}
|
||||
|
||||
const fileContent: string = await ServiceRepositoryUtil.getFileContent({
|
||||
filePath: filePath,
|
||||
});
|
||||
|
||||
const codeParts: string[] = await this.splitInputCode({
|
||||
code: fileContent,
|
||||
itemSize: 500,
|
||||
});
|
||||
|
||||
let newContent: string = "";
|
||||
|
||||
let hasSpansBeenAdded: boolean = true;
|
||||
|
||||
for (const codePart of codeParts) {
|
||||
const codePartResult: {
|
||||
newCode: string;
|
||||
hasSpansBeenAdded: boolean;
|
||||
} = await this.addSpansInCode({
|
||||
data: data,
|
||||
codePart: codePart,
|
||||
currentRetryCount: 0,
|
||||
maxRetryCount: 3,
|
||||
});
|
||||
|
||||
if (!codePartResult.hasSpansBeenAdded) {
|
||||
hasSpansBeenAdded = false;
|
||||
newContent += codePartResult.newCode + "\n";
|
||||
} else {
|
||||
newContent += codePart + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSpansBeenAdded) {
|
||||
this.isRequirementsMet = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
newContent = newContent.trim();
|
||||
|
||||
logger.debug("New Content:");
|
||||
logger.debug(newContent);
|
||||
|
||||
const fileActionProps: FileActionProp = data.actionProp as FileActionProp;
|
||||
|
||||
// add to result.
|
||||
data.result.files[fileActionProps.filePath] = {
|
||||
fileContent: newContent,
|
||||
} as CodeRepositoryFile;
|
||||
|
||||
this.isRequirementsMet = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
private async didPassValidation(data: CopilotPromptResult): Promise<boolean> {
|
||||
const validationResponse: string = data.output as string;
|
||||
if (validationResponse === "--no--") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async hasSpansBeenAddedAlready(content: string): Promise<boolean> {
|
||||
if (content.includes("--all-good--")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async addSpansInCode(options: {
|
||||
data: CopilotProcess;
|
||||
codePart: string;
|
||||
currentRetryCount: number;
|
||||
maxRetryCount: number;
|
||||
}): Promise<{
|
||||
newCode: string;
|
||||
hasSpansBeenAdded: boolean;
|
||||
}> {
|
||||
let hasSpansBeenAdded: boolean = true;
|
||||
|
||||
const codePart: string = options.codePart;
|
||||
const data: CopilotProcess = options.data;
|
||||
|
||||
const actionPrompt: CopilotActionPrompt = await this.getPrompt(
|
||||
data,
|
||||
codePart,
|
||||
);
|
||||
|
||||
const copilotResult: CopilotPromptResult =
|
||||
await this.askCopilot(actionPrompt);
|
||||
|
||||
const newCodePart: string = await this.cleanupCode({
|
||||
inputCode: codePart,
|
||||
outputCode: copilotResult.output as string,
|
||||
});
|
||||
|
||||
if (!(await this.hasSpansBeenAddedAlready(newCodePart))) {
|
||||
hasSpansBeenAdded = false;
|
||||
}
|
||||
|
||||
const validationPrompt: CopilotActionPrompt =
|
||||
await this.getValidationPrompt({
|
||||
oldCode: codePart,
|
||||
newCode: newCodePart,
|
||||
});
|
||||
|
||||
const validationResponse: CopilotPromptResult =
|
||||
await this.askCopilot(validationPrompt);
|
||||
|
||||
const didPassValidation: boolean =
|
||||
await this.didPassValidation(validationResponse);
|
||||
|
||||
if (
|
||||
!didPassValidation &&
|
||||
options.currentRetryCount < options.maxRetryCount
|
||||
) {
|
||||
return await this.addSpansInCode({
|
||||
data: data,
|
||||
codePart: codePart,
|
||||
currentRetryCount: options.currentRetryCount + 1,
|
||||
maxRetryCount: options.maxRetryCount,
|
||||
});
|
||||
}
|
||||
|
||||
if (!didPassValidation) {
|
||||
return {
|
||||
newCode: codePart,
|
||||
hasSpansBeenAdded: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
newCode: newCodePart,
|
||||
hasSpansBeenAdded: hasSpansBeenAdded,
|
||||
};
|
||||
}
|
||||
|
||||
private async getValidationPrompt(data: {
|
||||
oldCode: string;
|
||||
newCode: string;
|
||||
}): Promise<CopilotActionPrompt> {
|
||||
const oldCode: string = data.oldCode;
|
||||
const newCode: string = data.newCode;
|
||||
|
||||
const prompt: string = `
|
||||
I've asked to add open telemetry spans in the code.
|
||||
|
||||
This is the old code:
|
||||
|
||||
${oldCode}
|
||||
|
||||
----
|
||||
This is the new code:
|
||||
|
||||
${newCode}
|
||||
|
||||
Was anything changed in the code except adding spans? If yes, please reply with the following text:
|
||||
--yes--
|
||||
|
||||
If the code was NOT changed EXCEPT adding spans, please reply with the following text:
|
||||
--no--
|
||||
`;
|
||||
|
||||
const systemPrompt: string = await this.getSystemPrompt();
|
||||
|
||||
return {
|
||||
messages: [
|
||||
{
|
||||
content: systemPrompt,
|
||||
role: PromptRole.System,
|
||||
},
|
||||
{
|
||||
content: prompt,
|
||||
role: PromptRole.User,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
public override async getPrompt(
|
||||
_data: CopilotProcess,
|
||||
inputCode: string,
|
||||
): Promise<CopilotActionPrompt> {
|
||||
// const fileLanguage: TechStack = data.input.files[data.input.currentFilePath]
|
||||
// ?.fileLanguage as TechStack;
|
||||
|
||||
const fileLanguage: TechStack = TechStack.TypeScript;
|
||||
|
||||
const prompt: string = `Please add OpenTelemetry spans in the code to functions and methods. If spans are already added, do not modify them.
|
||||
|
||||
If you think functions in the code already have spans, please reply with the following text:
|
||||
--all-good--
|
||||
|
||||
Here is the code. This is in ${fileLanguage}:
|
||||
|
||||
${inputCode}
|
||||
`;
|
||||
|
||||
const systemPrompt: string = await this.getSystemPrompt();
|
||||
|
||||
return {
|
||||
messages: [
|
||||
{
|
||||
content: systemPrompt,
|
||||
role: PromptRole.System,
|
||||
},
|
||||
{
|
||||
content: prompt,
|
||||
role: PromptRole.User,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
public async getSystemPrompt(): Promise<string> {
|
||||
const systemPrompt: string = `You are an expert programmer. Here are your instructions:
|
||||
- You will follow the instructions given by the user strictly.
|
||||
- You will not deviate from the instructions given by the user.
|
||||
- You will not only add OpenTelemetry Spans in this code. You will not do anything else.`;
|
||||
|
||||
return systemPrompt;
|
||||
}
|
||||
|
||||
public async cleanupCode(data: {
|
||||
inputCode: string;
|
||||
outputCode: string;
|
||||
}): Promise<string> {
|
||||
// this code contains text as well. The code is in betwen ```<type> and ```. Please extract the code and return it.
|
||||
// for example code can be in the format of
|
||||
// ```python
|
||||
// print("Hello World")
|
||||
// ```
|
||||
|
||||
// so the code to be extracted is print("Hello World")
|
||||
|
||||
// the code can be in multiple lines as well.
|
||||
|
||||
let extractedCode: string = data.outputCode; // this is the code in the file
|
||||
|
||||
if (extractedCode.includes("```")) {
|
||||
extractedCode = extractedCode.match(/```.*\n([\s\S]*?)```/)?.[1] ?? "";
|
||||
}
|
||||
|
||||
// get first line of input code.
|
||||
|
||||
const firstWordOfInputCode: string = Text.getFirstWord(data.inputCode);
|
||||
extractedCode = Text.trimStartUntilThisWord(
|
||||
extractedCode,
|
||||
firstWordOfInputCode,
|
||||
);
|
||||
|
||||
const lastWordOfInputCode: string = Text.getLastWord(data.inputCode);
|
||||
|
||||
extractedCode = Text.trimEndUntilThisWord(
|
||||
extractedCode,
|
||||
lastWordOfInputCode,
|
||||
);
|
||||
|
||||
extractedCode = Text.trimUpQuotesFromStartAndEnd(extractedCode);
|
||||
|
||||
// check for quotes.
|
||||
|
||||
return extractedCode;
|
||||
}
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
import NotImplementedException from "Common/Types/Exception/NotImplementedException";
|
||||
import LlmType from "../../Types/LlmType";
|
||||
import CopilotActionType from "Common/Types/Copilot/CopilotActionType";
|
||||
import LLM from "../LLM/LLM";
|
||||
import { GetLlmType } from "../../Config";
|
||||
import Text from "Common/Types/Text";
|
||||
import { CopilotPromptResult } from "../LLM/LLMBase";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import CodeRepositoryUtil, { RepoScriptType } from "../../Utils/CodeRepository";
|
||||
import CopilotActionProp from "Common/Types/Copilot/CopilotActionProps/Index";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import {
|
||||
CopilotActionPrompt,
|
||||
CopilotProcess,
|
||||
CopilotProcessStart,
|
||||
} from "./Types";
|
||||
|
||||
export default class CopilotActionBase {
|
||||
public llmType: LlmType = LlmType.ONEUPTIME_LLM; // temp value which will be overridden in the constructor
|
||||
|
||||
public copilotActionType: CopilotActionType =
|
||||
CopilotActionType.IMPROVE_COMMENTS; // temp value which will be overridden in the constructor
|
||||
|
||||
public acceptFileExtentions: string[] = [];
|
||||
|
||||
public constructor() {
|
||||
this.llmType = GetLlmType();
|
||||
}
|
||||
|
||||
protected async isActionRequired(_data: {
|
||||
serviceCatalogId: ObjectID;
|
||||
serviceRepositoryId: ObjectID;
|
||||
copilotActionProp: CopilotActionProp;
|
||||
}): Promise<boolean> {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async getActionPropsToQueue(_data: {
|
||||
serviceCatalogId: ObjectID;
|
||||
serviceRepositoryId: ObjectID;
|
||||
maxActionsToQueue: number;
|
||||
}): Promise<Array<CopilotActionProp>> {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected async validateExecutionStep(
|
||||
_data: CopilotProcess,
|
||||
): Promise<boolean> {
|
||||
if (!this.copilotActionType) {
|
||||
throw new BadDataException("Copilot Action Type is not set");
|
||||
}
|
||||
|
||||
// validate by default.
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async onAfterExecute(
|
||||
data: CopilotProcess,
|
||||
): Promise<CopilotProcess> {
|
||||
// do nothing
|
||||
return data;
|
||||
}
|
||||
|
||||
protected async onBeforeExecute(
|
||||
data: CopilotProcess,
|
||||
): Promise<CopilotProcess> {
|
||||
// do nothing
|
||||
return data;
|
||||
}
|
||||
|
||||
public async getBranchName(): Promise<string> {
|
||||
const randomText: string = Text.generateRandomText(5);
|
||||
const bracnhName: string = `${Text.pascalCaseToDashes(this.copilotActionType).toLowerCase()}-${randomText}`;
|
||||
// replace -- with - in the branch name
|
||||
return Text.replaceAll(bracnhName, "--", "-");
|
||||
}
|
||||
|
||||
public async getPullRequestTitle(_data: CopilotProcess): Promise<string> {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async getPullRequestBody(_data: CopilotProcess): Promise<string> {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected async getDefaultPullRequestBody(): Promise<string> {
|
||||
return `
|
||||
|
||||
#### Warning
|
||||
This PR is generated by OneUptime Copilot. OneUptime Copilot is an AI tool that improves your code. Please do not rely on it completely. Always review the changes before merging.
|
||||
|
||||
#### Feedback
|
||||
If you have any feedback or suggestions, please let us know. We would love to hear from you. Please contact us at copilot@oneuptime.com.
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
public async getCommitMessage(_data: CopilotProcess): Promise<string> {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected async onExecutionStep(
|
||||
data: CopilotProcess,
|
||||
): Promise<CopilotProcess> {
|
||||
return Promise.resolve(data);
|
||||
}
|
||||
|
||||
protected async isActionComplete(_data: CopilotProcess): Promise<boolean> {
|
||||
return true; // by default the action is completed
|
||||
}
|
||||
|
||||
protected async getNextFilePath(
|
||||
_data: CopilotProcess,
|
||||
): Promise<string | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
public async execute(
|
||||
data: CopilotProcessStart,
|
||||
): Promise<CopilotProcess | null> {
|
||||
logger.info(
|
||||
"Executing Copilot Action (this will take several minutes to complete): " +
|
||||
this.copilotActionType,
|
||||
);
|
||||
|
||||
logger.info(data.actionProp);
|
||||
|
||||
const onBeforeExecuteActionScript: string | null =
|
||||
await CodeRepositoryUtil.getRepoScript({
|
||||
scriptType: RepoScriptType.OnBeforeCodeChange,
|
||||
});
|
||||
|
||||
if (!onBeforeExecuteActionScript) {
|
||||
logger.debug(
|
||||
"No on-before-copilot-action script found for this repository.",
|
||||
);
|
||||
} else {
|
||||
logger.info("Executing on-before-copilot-action script.");
|
||||
await CodeRepositoryUtil.executeScript({
|
||||
script: onBeforeExecuteActionScript,
|
||||
});
|
||||
logger.info("on-before-copilot-action script executed successfully");
|
||||
}
|
||||
|
||||
const processData: CopilotProcess = await this.onBeforeExecute({
|
||||
...data,
|
||||
result: {
|
||||
files: {},
|
||||
statusMessage: "",
|
||||
logs: [],
|
||||
},
|
||||
});
|
||||
|
||||
if (!processData.result) {
|
||||
processData.result = {
|
||||
files: {},
|
||||
statusMessage: "",
|
||||
logs: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!processData.result.files) {
|
||||
processData.result.files = {};
|
||||
}
|
||||
|
||||
let isActionComplete: boolean = false;
|
||||
|
||||
while (!isActionComplete) {
|
||||
if (!(await this.validateExecutionStep(processData))) {
|
||||
// execution step not valid
|
||||
// return data as it is
|
||||
|
||||
return processData;
|
||||
}
|
||||
|
||||
data = await this.onExecutionStep(processData);
|
||||
|
||||
isActionComplete = await this.isActionComplete(processData);
|
||||
}
|
||||
|
||||
data = await this.onAfterExecute(processData);
|
||||
|
||||
// write to disk.
|
||||
await this.writeToDisk({ data: processData });
|
||||
|
||||
const onAfterExecuteActionScript: string | null =
|
||||
await CodeRepositoryUtil.getRepoScript({
|
||||
scriptType: RepoScriptType.OnAfterCodeChange,
|
||||
});
|
||||
|
||||
if (!onAfterExecuteActionScript) {
|
||||
logger.debug(
|
||||
"No on-after-copilot-action script found for this repository.",
|
||||
);
|
||||
}
|
||||
|
||||
if (onAfterExecuteActionScript) {
|
||||
logger.info("Executing on-after-copilot-action script.");
|
||||
await CodeRepositoryUtil.executeScript({
|
||||
script: onAfterExecuteActionScript,
|
||||
});
|
||||
logger.info("on-after-copilot-action script executed successfully");
|
||||
}
|
||||
|
||||
return processData;
|
||||
}
|
||||
|
||||
protected async _getPrompt(
|
||||
data: CopilotProcess,
|
||||
inputCode: string,
|
||||
): Promise<CopilotActionPrompt | null> {
|
||||
const prompt: CopilotActionPrompt | null = await this._getPrompt(
|
||||
data,
|
||||
inputCode,
|
||||
);
|
||||
|
||||
if (!prompt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
protected async getPrompt(
|
||||
_data: CopilotProcess,
|
||||
_inputCode: string,
|
||||
): Promise<CopilotActionPrompt | null> {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected async askCopilot(
|
||||
prompt: CopilotActionPrompt,
|
||||
): Promise<CopilotPromptResult> {
|
||||
return await LLM.getResponse(prompt);
|
||||
}
|
||||
|
||||
protected async writeToDisk(data: { data: CopilotProcess }): Promise<void> {
|
||||
// write all the modified files.
|
||||
|
||||
const processResult: CopilotProcess = data.data;
|
||||
|
||||
for (const filePath in processResult.result.files) {
|
||||
logger.info(`Writing file: ${filePath}`);
|
||||
logger.info(`File content: `);
|
||||
logger.info(`${processResult.result.files[filePath]!.fileContent}`);
|
||||
|
||||
const code: string = processResult.result.files[filePath]!.fileContent;
|
||||
|
||||
await CodeRepositoryUtil.writeToFile({
|
||||
filePath: filePath,
|
||||
content: code,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected async discardAllChanges(): Promise<void> {
|
||||
await CodeRepositoryUtil.discardAllChangesOnCurrentBranch();
|
||||
}
|
||||
|
||||
protected async splitInputCode(data: {
|
||||
code: string;
|
||||
itemSize: number;
|
||||
}): Promise<string[]> {
|
||||
const inputCode: string = data.code;
|
||||
|
||||
const items: Array<string> = [];
|
||||
|
||||
const linesInInputCode: Array<string> = inputCode.split("\n");
|
||||
|
||||
let currentItemSize: number = 0;
|
||||
const maxItemSize: number = data.itemSize;
|
||||
|
||||
let currentItem: string = "";
|
||||
|
||||
for (const line of linesInInputCode) {
|
||||
const words: Array<string> = line.split(" ");
|
||||
|
||||
// check if the current item size is less than the max item size
|
||||
if (currentItemSize + words.length < maxItemSize) {
|
||||
currentItem += line + "\n";
|
||||
currentItemSize += words.length;
|
||||
} else {
|
||||
// start a new item
|
||||
items.push(currentItem);
|
||||
currentItem = line + "\n";
|
||||
currentItemSize = words.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentItem) {
|
||||
items.push(currentItem);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
@@ -1,442 +0,0 @@
|
||||
import CopilotActionType from "Common/Types/Copilot/CopilotActionType";
|
||||
import CopilotActionBase from "./CopilotActionsBase";
|
||||
import CodeRepositoryUtil from "../../Utils/CodeRepository";
|
||||
import TechStack from "Common/Types/ServiceCatalog/TechStack";
|
||||
import { CopilotPromptResult } from "../LLM/LLMBase";
|
||||
import Text from "Common/Types/Text";
|
||||
import { CopilotActionPrompt, CopilotProcess } from "./Types";
|
||||
import { PromptRole } from "../LLM/Prompt";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import FileActionProp from "Common/Types/Copilot/CopilotActionProps/FileActionProp";
|
||||
import CodeRepositoryFile from "Common/Server/Utils/CodeRepository/CodeRepositoryFile";
|
||||
import CopilotActionUtil from "../../Utils/CopilotAction";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import CopilotAction from "Common/Models/DatabaseModels/CopilotAction";
|
||||
import ServiceRepositoryUtil from "../../Utils/ServiceRepository";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import ArrayUtil from "Common/Utils/Array";
|
||||
import CopilotActionProp from "Common/Types/Copilot/CopilotActionProps/Index";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
|
||||
export default class ImproveComments extends CopilotActionBase {
|
||||
public isRequirementsMet: boolean = false;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
this.copilotActionType = CopilotActionType.IMPROVE_COMMENTS;
|
||||
this.acceptFileExtentions = CodeRepositoryUtil.getCodeFileExtentions();
|
||||
}
|
||||
|
||||
protected override async isActionRequired(data: {
|
||||
serviceCatalogId: ObjectID;
|
||||
serviceRepositoryId: ObjectID;
|
||||
copilotActionProp: FileActionProp;
|
||||
}): Promise<boolean> {
|
||||
// check if the action has already been processed for this file.
|
||||
const existingAction: CopilotAction | null =
|
||||
await CopilotActionUtil.getExistingAction({
|
||||
serviceCatalogId: data.serviceCatalogId,
|
||||
actionType: this.copilotActionType,
|
||||
actionProps: {
|
||||
filePath: data.copilotActionProp.filePath, // has this action run on this file before?
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingAction) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override async getActionPropsToQueue(data: {
|
||||
serviceCatalogId: ObjectID;
|
||||
serviceRepositoryId: ObjectID;
|
||||
maxActionsToQueue: number;
|
||||
}): Promise<Array<CopilotActionProp>> {
|
||||
// get files in the repo.
|
||||
logger.debug(
|
||||
`${this.copilotActionType} - Getting files to queue for improve comments.`,
|
||||
);
|
||||
|
||||
let totalActionsToQueue: number = 0;
|
||||
|
||||
logger.debug(`${this.copilotActionType} - Reading files in the service.`);
|
||||
|
||||
const files: Dictionary<CodeRepositoryFile> =
|
||||
await ServiceRepositoryUtil.getFilesByServiceCatalogId({
|
||||
serviceCatalogId: data.serviceCatalogId,
|
||||
});
|
||||
|
||||
logger.debug(
|
||||
`${this.copilotActionType} - Files read. ${Object.keys(files).length} files found.`,
|
||||
);
|
||||
|
||||
// get keys in random order.
|
||||
let fileKeys: string[] = Object.keys(files);
|
||||
|
||||
//randomize the order of the files.
|
||||
fileKeys = ArrayUtil.shuffle(fileKeys);
|
||||
|
||||
const actionsPropsQueued: Array<CopilotActionProp> = [];
|
||||
|
||||
logger.debug(
|
||||
`${this.copilotActionType} - Accepted File Extentions: ${this.acceptFileExtentions}`,
|
||||
);
|
||||
|
||||
for (const fileKey of fileKeys) {
|
||||
logger.debug(
|
||||
`${this.copilotActionType} - Checking file: ${files[fileKey]!.filePath}`,
|
||||
);
|
||||
|
||||
// check if the file is in accepted file extentions.
|
||||
const fileExtention: string = LocalFile.getFileExtension(
|
||||
files[fileKey]!.filePath,
|
||||
);
|
||||
|
||||
logger.debug(
|
||||
`${this.copilotActionType} - File Extention: ${fileExtention}`,
|
||||
);
|
||||
|
||||
if (!this.acceptFileExtentions.includes(fileExtention)) {
|
||||
logger.debug(
|
||||
`${this.copilotActionType} - File is not in accepted file extentions. Skipping.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const file: CodeRepositoryFile = files[fileKey]!;
|
||||
|
||||
logger.debug(
|
||||
`${this.copilotActionType} - Checking file: ${file.filePath}`,
|
||||
);
|
||||
|
||||
if (
|
||||
await this.isActionRequired({
|
||||
serviceCatalogId: data.serviceCatalogId,
|
||||
serviceRepositoryId: data.serviceRepositoryId,
|
||||
copilotActionProp: {
|
||||
filePath: file.filePath,
|
||||
},
|
||||
})
|
||||
) {
|
||||
actionsPropsQueued.push({
|
||||
filePath: file.filePath,
|
||||
});
|
||||
|
||||
totalActionsToQueue++;
|
||||
}
|
||||
|
||||
if (totalActionsToQueue >= data.maxActionsToQueue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return actionsPropsQueued;
|
||||
}
|
||||
|
||||
public override async getCommitMessage(
|
||||
data: CopilotProcess,
|
||||
): Promise<string> {
|
||||
return (
|
||||
"Improved comments on " + (data.actionProp as FileActionProp).filePath
|
||||
);
|
||||
}
|
||||
|
||||
public override async getPullRequestTitle(
|
||||
data: CopilotProcess,
|
||||
): Promise<string> {
|
||||
return (
|
||||
"Improved comments on " + (data.actionProp as FileActionProp).filePath
|
||||
);
|
||||
}
|
||||
|
||||
public override async getPullRequestBody(
|
||||
data: CopilotProcess,
|
||||
): Promise<string> {
|
||||
return `Improved comments on ${(data.actionProp as FileActionProp).filePath}
|
||||
|
||||
${await this.getDefaultPullRequestBody()}
|
||||
`;
|
||||
}
|
||||
|
||||
public override isActionComplete(_data: CopilotProcess): Promise<boolean> {
|
||||
return Promise.resolve(this.isRequirementsMet);
|
||||
}
|
||||
|
||||
public override async onExecutionStep(
|
||||
data: CopilotProcess,
|
||||
): Promise<CopilotProcess> {
|
||||
const filePath: string = (data.actionProp as FileActionProp).filePath;
|
||||
|
||||
if (!filePath) {
|
||||
throw new BadDataException("File Path is not set in the action prop.");
|
||||
}
|
||||
|
||||
const fileContent: string = await ServiceRepositoryUtil.getFileContent({
|
||||
filePath: filePath,
|
||||
});
|
||||
|
||||
const codeParts: string[] = await this.splitInputCode({
|
||||
code: fileContent,
|
||||
itemSize: 500,
|
||||
});
|
||||
|
||||
let newContent: string = "";
|
||||
|
||||
let isWellCommented: boolean = true;
|
||||
|
||||
for (const codePart of codeParts) {
|
||||
const codePartResult: {
|
||||
newCode: string;
|
||||
isWellCommented: boolean;
|
||||
} = await this.commentCodePart({
|
||||
data: data,
|
||||
codePart: codePart,
|
||||
currentRetryCount: 0,
|
||||
maxRetryCount: 3,
|
||||
});
|
||||
|
||||
if (!codePartResult.isWellCommented) {
|
||||
isWellCommented = false;
|
||||
newContent += codePartResult.newCode + "\n";
|
||||
} else {
|
||||
newContent += codePart + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (isWellCommented) {
|
||||
this.isRequirementsMet = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
newContent = newContent.trim();
|
||||
|
||||
logger.debug("New Content:");
|
||||
logger.debug(newContent);
|
||||
|
||||
const fileActionProps: FileActionProp = data.actionProp as FileActionProp;
|
||||
|
||||
// add to result.
|
||||
data.result.files[fileActionProps.filePath] = {
|
||||
fileContent: newContent,
|
||||
} as CodeRepositoryFile;
|
||||
|
||||
this.isRequirementsMet = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
private async didPassValidation(data: CopilotPromptResult): Promise<boolean> {
|
||||
const validationResponse: string = data.output as string;
|
||||
if (validationResponse === "--no--") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async isFileAlreadyWellCommented(content: string): Promise<boolean> {
|
||||
if (content.includes("--all-good--")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async commentCodePart(options: {
|
||||
data: CopilotProcess;
|
||||
codePart: string;
|
||||
currentRetryCount: number;
|
||||
maxRetryCount: number;
|
||||
}): Promise<{
|
||||
newCode: string;
|
||||
isWellCommented: boolean;
|
||||
}> {
|
||||
let isWellCommented: boolean = true;
|
||||
|
||||
const codePart: string = options.codePart;
|
||||
const data: CopilotProcess = options.data;
|
||||
|
||||
const actionPrompt: CopilotActionPrompt = await this.getPrompt(
|
||||
data,
|
||||
codePart,
|
||||
);
|
||||
|
||||
const copilotResult: CopilotPromptResult =
|
||||
await this.askCopilot(actionPrompt);
|
||||
|
||||
const newCodePart: string = await this.cleanupCode({
|
||||
inputCode: codePart,
|
||||
outputCode: copilotResult.output as string,
|
||||
});
|
||||
|
||||
if (!(await this.isFileAlreadyWellCommented(newCodePart))) {
|
||||
isWellCommented = false;
|
||||
}
|
||||
|
||||
const validationPrompt: CopilotActionPrompt =
|
||||
await this.getValidationPrompt({
|
||||
oldCode: codePart,
|
||||
newCode: newCodePart,
|
||||
});
|
||||
|
||||
const validationResponse: CopilotPromptResult =
|
||||
await this.askCopilot(validationPrompt);
|
||||
|
||||
const didPassValidation: boolean =
|
||||
await this.didPassValidation(validationResponse);
|
||||
|
||||
if (
|
||||
!didPassValidation &&
|
||||
options.currentRetryCount < options.maxRetryCount
|
||||
) {
|
||||
return await this.commentCodePart({
|
||||
data: data,
|
||||
codePart: codePart,
|
||||
currentRetryCount: options.currentRetryCount + 1,
|
||||
maxRetryCount: options.maxRetryCount,
|
||||
});
|
||||
}
|
||||
|
||||
if (!didPassValidation) {
|
||||
return {
|
||||
newCode: codePart,
|
||||
isWellCommented: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
newCode: newCodePart,
|
||||
isWellCommented: isWellCommented,
|
||||
};
|
||||
}
|
||||
|
||||
private async getValidationPrompt(data: {
|
||||
oldCode: string;
|
||||
newCode: string;
|
||||
}): Promise<CopilotActionPrompt> {
|
||||
const oldCode: string = data.oldCode;
|
||||
const newCode: string = data.newCode;
|
||||
|
||||
const prompt: string = `
|
||||
I've asked to improve comments in the code.
|
||||
|
||||
This is the old code:
|
||||
|
||||
${oldCode}
|
||||
|
||||
----
|
||||
This is the new code:
|
||||
|
||||
${newCode}
|
||||
|
||||
Was anything changed in the code except comments? If yes, please reply with the following text:
|
||||
--yes--
|
||||
|
||||
If the code was NOT changed EXCEPT comments, please reply with the following text:
|
||||
--no--
|
||||
`;
|
||||
|
||||
const systemPrompt: string = await this.getSystemPrompt();
|
||||
|
||||
return {
|
||||
messages: [
|
||||
{
|
||||
content: systemPrompt,
|
||||
role: PromptRole.System,
|
||||
},
|
||||
{
|
||||
content: prompt,
|
||||
role: PromptRole.User,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
public override async getPrompt(
|
||||
_data: CopilotProcess,
|
||||
inputCode: string,
|
||||
): Promise<CopilotActionPrompt> {
|
||||
// const fileLanguage: TechStack = data.input.files[data.input.currentFilePath]
|
||||
// ?.fileLanguage as TechStack;
|
||||
|
||||
const fileLanguage: TechStack = TechStack.TypeScript;
|
||||
|
||||
const prompt: string = `Please improve the comments in this code. Please only add minimal comments and comment code which is hard to understand. Please add comments in new line and do not add inline comments.
|
||||
|
||||
If you think the code is already well commented, please reply with the following text:
|
||||
--all-good--
|
||||
|
||||
Here is the code. This is in ${fileLanguage}:
|
||||
|
||||
${inputCode}
|
||||
`;
|
||||
|
||||
const systemPrompt: string = await this.getSystemPrompt();
|
||||
|
||||
return {
|
||||
messages: [
|
||||
{
|
||||
content: systemPrompt,
|
||||
role: PromptRole.System,
|
||||
},
|
||||
{
|
||||
content: prompt,
|
||||
role: PromptRole.User,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
public async getSystemPrompt(): Promise<string> {
|
||||
const systemPrompt: string = `You are an expert programmer. Here are your instructions:
|
||||
- You will follow the instructions given by the user strictly.
|
||||
- You will not deviate from the instructions given by the user.
|
||||
- You will not change the code. You will only improve the comments.`;
|
||||
|
||||
return systemPrompt;
|
||||
}
|
||||
|
||||
public async cleanupCode(data: {
|
||||
inputCode: string;
|
||||
outputCode: string;
|
||||
}): Promise<string> {
|
||||
// this code contains text as well. The code is in betwen ```<type> and ```. Please extract the code and return it.
|
||||
// for example code can be in the format of
|
||||
// ```python
|
||||
// print("Hello World")
|
||||
// ```
|
||||
|
||||
// so the code to be extracted is print("Hello World")
|
||||
|
||||
// the code can be in multiple lines as well.
|
||||
|
||||
let extractedCode: string = data.outputCode; // this is the code in the file
|
||||
|
||||
if (extractedCode.includes("```")) {
|
||||
extractedCode = extractedCode.match(/```.*\n([\s\S]*?)```/)?.[1] ?? "";
|
||||
}
|
||||
|
||||
// get first line of input code.
|
||||
|
||||
const firstWordOfInputCode: string = Text.getFirstWord(data.inputCode);
|
||||
extractedCode = Text.trimStartUntilThisWord(
|
||||
extractedCode,
|
||||
firstWordOfInputCode,
|
||||
);
|
||||
|
||||
const lastWordOfInputCode: string = Text.getLastWord(data.inputCode);
|
||||
|
||||
extractedCode = Text.trimEndUntilThisWord(
|
||||
extractedCode,
|
||||
lastWordOfInputCode,
|
||||
);
|
||||
|
||||
extractedCode = Text.trimUpQuotesFromStartAndEnd(extractedCode);
|
||||
|
||||
// check for quotes.
|
||||
|
||||
return extractedCode;
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
import CopilotActionType from "Common/Types/Copilot/CopilotActionType";
|
||||
import ImproveComments from "./ImproveComments";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import CopilotActionBase from "./CopilotActionsBase";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import CodeRepositoryUtil, { RepoScriptType } from "../../Utils/CodeRepository";
|
||||
import ServiceCopilotCodeRepository from "Common/Models/DatabaseModels/ServiceCopilotCodeRepository";
|
||||
import PullRequest from "Common/Types/CodeRepository/PullRequest";
|
||||
import CopilotAction from "Common/Models/DatabaseModels/CopilotAction";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import CopilotActionStatus from "Common/Types/Copilot/CopilotActionStatus";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import CopilotPullRequest from "Common/Models/DatabaseModels/CopilotPullRequest";
|
||||
import CopilotPullRequestService from "../CopilotPullRequest";
|
||||
import CopilotActionUtil from "../../Utils/CopilotAction";
|
||||
import { CopilotProcess } from "./Types";
|
||||
// import AddSpans from "./AddSpan";
|
||||
|
||||
export const ActionDictionary: Dictionary<typeof CopilotActionBase> = {
|
||||
[CopilotActionType.IMPROVE_COMMENTS]: ImproveComments,
|
||||
// [CopilotActionType.ADD_SPANS]: AddSpans,
|
||||
};
|
||||
|
||||
export interface CopilotExecutionResult {
|
||||
status: CopilotActionStatus;
|
||||
pullRequest: PullRequest | null;
|
||||
}
|
||||
|
||||
export default class CopilotActionService {
|
||||
public static async executeAction(data: {
|
||||
serviceRepository: ServiceCopilotCodeRepository;
|
||||
copilotAction: CopilotAction;
|
||||
}): Promise<CopilotExecutionResult> {
|
||||
await CodeRepositoryUtil.discardAllChangesOnCurrentBranch();
|
||||
|
||||
await CodeRepositoryUtil.switchToMainBranch();
|
||||
|
||||
await CodeRepositoryUtil.pullChanges();
|
||||
|
||||
const ActionType: typeof CopilotActionBase | undefined =
|
||||
ActionDictionary[data.copilotAction.copilotActionType!];
|
||||
|
||||
if (!ActionType) {
|
||||
throw new BadDataException("Invalid CopilotActionType");
|
||||
}
|
||||
|
||||
const action: CopilotActionBase = new ActionType() as CopilotActionBase;
|
||||
|
||||
// mark this action as processing.
|
||||
await CopilotActionUtil.updateCopilotAction({
|
||||
actionStatus: CopilotActionStatus.PROCESSING,
|
||||
actionId: data.copilotAction.id!,
|
||||
});
|
||||
|
||||
const processResult: CopilotProcess | null = await action.execute({
|
||||
actionProp: data.copilotAction.copilotActionProp!,
|
||||
});
|
||||
|
||||
let executionResult: CopilotExecutionResult = {
|
||||
status: CopilotActionStatus.NO_ACTION_REQUIRED,
|
||||
pullRequest: null,
|
||||
};
|
||||
|
||||
let pullRequest: PullRequest | null = null;
|
||||
|
||||
if (
|
||||
processResult &&
|
||||
processResult.result &&
|
||||
processResult.result.files &&
|
||||
Object.keys(processResult.result.files).length > 0
|
||||
) {
|
||||
logger.info("Obtained result from Copilot Action");
|
||||
logger.info("Committing the changes to the repository and creating a PR");
|
||||
|
||||
const branchName: string = CodeRepositoryUtil.getBranchName({
|
||||
branchName: await action.getBranchName(),
|
||||
});
|
||||
|
||||
// create a branch
|
||||
|
||||
await CodeRepositoryUtil.createBranch({
|
||||
branchName: branchName,
|
||||
});
|
||||
|
||||
// write all the modified files.
|
||||
const filePaths: string[] = Object.keys(processResult.result.files);
|
||||
|
||||
// run on before commit script. This is the place where we can run tests.
|
||||
|
||||
const onBeforeCommitScript: string | null =
|
||||
await CodeRepositoryUtil.getRepoScript({
|
||||
scriptType: RepoScriptType.OnBeforeCommit,
|
||||
});
|
||||
|
||||
if (!onBeforeCommitScript) {
|
||||
logger.debug("No on-before-commit script found for this repository.");
|
||||
} else {
|
||||
logger.info("Executing on-before-commit script.");
|
||||
await CodeRepositoryUtil.executeScript({
|
||||
script: onBeforeCommitScript,
|
||||
});
|
||||
logger.info("on-before-commit script executed successfully.");
|
||||
}
|
||||
|
||||
const commitMessage: string =
|
||||
await action.getCommitMessage(processResult);
|
||||
|
||||
const onAfterCommitScript: string | null =
|
||||
await CodeRepositoryUtil.getRepoScript({
|
||||
scriptType: RepoScriptType.OnAfterCommit,
|
||||
});
|
||||
|
||||
if (!onAfterCommitScript) {
|
||||
logger.debug("No on-after-commit script found for this repository.");
|
||||
}
|
||||
|
||||
if (onAfterCommitScript) {
|
||||
logger.info("Executing on-after-commit script.");
|
||||
await CodeRepositoryUtil.executeScript({
|
||||
script: onAfterCommitScript,
|
||||
});
|
||||
logger.info("on-after-commit script executed successfully.");
|
||||
}
|
||||
|
||||
// add files to stage
|
||||
|
||||
logger.info("Adding files to stage: ");
|
||||
|
||||
for (const filePath of filePaths) {
|
||||
logger.info(`- ${filePath}`);
|
||||
}
|
||||
|
||||
await CodeRepositoryUtil.addFilesToGit({
|
||||
filePaths: filePaths,
|
||||
});
|
||||
|
||||
// commit changes
|
||||
logger.info("Committing changes");
|
||||
await CodeRepositoryUtil.commitChanges({
|
||||
message: commitMessage,
|
||||
});
|
||||
|
||||
// push changes
|
||||
logger.info("Pushing changes");
|
||||
await CodeRepositoryUtil.pushChanges({
|
||||
branchName: branchName,
|
||||
});
|
||||
|
||||
// create a PR
|
||||
logger.info("Creating a PR");
|
||||
pullRequest = await CodeRepositoryUtil.createPullRequest({
|
||||
branchName: branchName,
|
||||
title: await action.getPullRequestTitle(processResult),
|
||||
body: await action.getPullRequestBody(processResult),
|
||||
});
|
||||
|
||||
// switch to main branch.
|
||||
logger.info("Switching to main branch");
|
||||
await CodeRepositoryUtil.switchToMainBranch();
|
||||
|
||||
//save the result to the database.
|
||||
logger.info("Saving the result to the database");
|
||||
executionResult = {
|
||||
status: CopilotActionStatus.PR_CREATED,
|
||||
pullRequest: pullRequest,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
!processResult ||
|
||||
!processResult.result ||
|
||||
!processResult.result.files ||
|
||||
Object.keys(processResult.result.files).length === 0
|
||||
) {
|
||||
logger.info("No result obtained from Copilot Action");
|
||||
}
|
||||
|
||||
const getCurrentCommitHash: string =
|
||||
await CodeRepositoryUtil.getCurrentCommitHash();
|
||||
|
||||
await CopilotActionService.updateCopilotAction({
|
||||
serviceCatalogId: data.serviceRepository.serviceCatalog!.id!,
|
||||
serviceRepositoryId: data.serviceRepository.id!,
|
||||
commitHash: getCurrentCommitHash,
|
||||
pullRequest: pullRequest,
|
||||
copilotActionStatus: executionResult.status,
|
||||
copilotActonId: data.copilotAction.id!,
|
||||
statusMessage: processResult?.result.statusMessage || "",
|
||||
logs: processResult?.result.logs || [],
|
||||
});
|
||||
|
||||
return executionResult;
|
||||
}
|
||||
|
||||
private static async updateCopilotAction(data: {
|
||||
copilotActonId: ObjectID;
|
||||
serviceCatalogId: ObjectID;
|
||||
serviceRepositoryId: ObjectID;
|
||||
commitHash: string;
|
||||
pullRequest: PullRequest | null;
|
||||
statusMessage: string;
|
||||
logs: Array<string>;
|
||||
copilotActionStatus: CopilotActionStatus;
|
||||
}): Promise<void> {
|
||||
// add copilot action to the database.
|
||||
|
||||
let copilotPullRequest: CopilotPullRequest | null = null;
|
||||
|
||||
if (data.pullRequest) {
|
||||
copilotPullRequest =
|
||||
await CopilotPullRequestService.addPullRequestToDatabase({
|
||||
pullRequest: data.pullRequest,
|
||||
serviceCatalogId: data.serviceCatalogId,
|
||||
serviceRepositoryId: data.serviceRepositoryId,
|
||||
});
|
||||
}
|
||||
|
||||
await CopilotActionUtil.updateCopilotAction({
|
||||
actionStatus: data.copilotActionStatus,
|
||||
pullRequestId: copilotPullRequest ? copilotPullRequest.id! : undefined,
|
||||
commitHash: data.commitHash,
|
||||
statusMessage: data.statusMessage,
|
||||
logs: data.logs,
|
||||
actionId: data.copilotActonId,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import CodeRepositoryFile from "Common/Server/Utils/CodeRepository/CodeRepositoryFile";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import { Prompt } from "../LLM/Prompt";
|
||||
import CopilotActionProp from "Common/Types/Copilot/CopilotActionProps/Index";
|
||||
|
||||
export interface CopilotActionRunResult {
|
||||
files: Dictionary<CodeRepositoryFile>;
|
||||
statusMessage: string;
|
||||
logs: Array<string>;
|
||||
}
|
||||
|
||||
export interface CopilotActionPrompt {
|
||||
messages: Array<Prompt>;
|
||||
timeoutInMinutes?: number | undefined;
|
||||
}
|
||||
|
||||
export interface CopilotActionVars {
|
||||
currentFilePath: string;
|
||||
files: Dictionary<CodeRepositoryFile>;
|
||||
}
|
||||
|
||||
export interface CopilotProcessStart {
|
||||
actionProp: CopilotActionProp;
|
||||
}
|
||||
|
||||
export interface CopilotProcess extends CopilotProcessStart {
|
||||
result: CopilotActionRunResult;
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import PullRequest from "Common/Types/CodeRepository/PullRequest";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { GetOneUptimeURL, GetRepositorySecretKey } from "../Config";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import API from "Common/Utils/API";
|
||||
import CopilotPullRequest from "Common/Models/DatabaseModels/CopilotPullRequest";
|
||||
import CodeRepositoryUtil from "../Utils/CodeRepository";
|
||||
import PullRequestState from "Common/Types/CodeRepository/PullRequestState";
|
||||
|
||||
export default class CopilotPullRequestService {
|
||||
public static async refreshPullRequestStatus(data: {
|
||||
copilotPullRequest: CopilotPullRequest;
|
||||
}): Promise<PullRequestState> {
|
||||
if (!data.copilotPullRequest.pullRequestId) {
|
||||
throw new BadDataException("Pull Request ID not found");
|
||||
}
|
||||
|
||||
if (!data.copilotPullRequest.id) {
|
||||
throw new BadDataException("Copilot Pull Request ID not found");
|
||||
}
|
||||
|
||||
const currentState: PullRequestState =
|
||||
await CodeRepositoryUtil.getPullRequestState({
|
||||
pullRequestId: data.copilotPullRequest.pullRequestId,
|
||||
});
|
||||
|
||||
// update the status of the pull request in the database.
|
||||
|
||||
const url: URL = URL.fromString(
|
||||
GetOneUptimeURL().toString() + "/api",
|
||||
).addRoute(
|
||||
`${new CopilotPullRequest()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/update-pull-request-status/${GetRepositorySecretKey()}`,
|
||||
);
|
||||
|
||||
const codeRepositoryResult: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.post(url, {
|
||||
copilotPullRequestId: data.copilotPullRequest.id?.toString(),
|
||||
copilotPullRequestStatus: currentState,
|
||||
});
|
||||
|
||||
if (codeRepositoryResult instanceof HTTPErrorResponse) {
|
||||
throw codeRepositoryResult;
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
public static async getOpenPullRequestsFromDatabase(): Promise<
|
||||
Array<CopilotPullRequest>
|
||||
> {
|
||||
// send this to the API.
|
||||
const url: URL = URL.fromString(
|
||||
GetOneUptimeURL().toString() + "/api",
|
||||
).addRoute(
|
||||
`${new CopilotPullRequest()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/get-pending-pull-requests/${GetRepositorySecretKey()}`,
|
||||
);
|
||||
|
||||
const codeRepositoryResult: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.get(url);
|
||||
|
||||
if (codeRepositoryResult instanceof HTTPErrorResponse) {
|
||||
throw codeRepositoryResult;
|
||||
}
|
||||
|
||||
const copilotPullRequestsJsonArray: Array<JSONObject> = codeRepositoryResult
|
||||
.data["copilotPullRequests"] as Array<JSONObject>;
|
||||
return CopilotPullRequest.fromJSONArray(
|
||||
copilotPullRequestsJsonArray,
|
||||
CopilotPullRequest,
|
||||
) as Array<CopilotPullRequest>;
|
||||
}
|
||||
|
||||
public static async addPullRequestToDatabase(data: {
|
||||
pullRequest: PullRequest;
|
||||
serviceCatalogId?: ObjectID | undefined;
|
||||
serviceRepositoryId?: ObjectID | undefined;
|
||||
isSetupPullRequest?: boolean | undefined;
|
||||
}): Promise<CopilotPullRequest> {
|
||||
let copilotPullRequest: CopilotPullRequest | null = null;
|
||||
|
||||
if (data.pullRequest && data.pullRequest.pullRequestNumber) {
|
||||
copilotPullRequest = new CopilotPullRequest();
|
||||
copilotPullRequest.pullRequestId =
|
||||
data.pullRequest.pullRequestNumber.toString();
|
||||
copilotPullRequest.copilotPullRequestStatus = PullRequestState.Open;
|
||||
|
||||
if (data.serviceCatalogId) {
|
||||
copilotPullRequest.serviceCatalogId = data.serviceCatalogId;
|
||||
}
|
||||
|
||||
if (data.isSetupPullRequest) {
|
||||
copilotPullRequest.isSetupPullRequest = data.isSetupPullRequest;
|
||||
}
|
||||
|
||||
if (data.serviceRepositoryId) {
|
||||
copilotPullRequest.serviceRepositoryId = data.serviceRepositoryId;
|
||||
}
|
||||
|
||||
// send this to the API.
|
||||
const url: URL = URL.fromString(
|
||||
GetOneUptimeURL().toString() + "/api",
|
||||
).addRoute(
|
||||
`${new CopilotPullRequest()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/add-pull-request/${GetRepositorySecretKey()}`,
|
||||
);
|
||||
|
||||
const codeRepositoryResult: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.post(url, {
|
||||
copilotPullRequest: CopilotPullRequest.toJSON(
|
||||
copilotPullRequest,
|
||||
CopilotPullRequest,
|
||||
),
|
||||
});
|
||||
|
||||
if (codeRepositoryResult instanceof HTTPErrorResponse) {
|
||||
throw codeRepositoryResult;
|
||||
}
|
||||
|
||||
copilotPullRequest = CopilotPullRequest.fromJSON(
|
||||
codeRepositoryResult.data,
|
||||
CopilotPullRequest,
|
||||
) as CopilotPullRequest;
|
||||
|
||||
return copilotPullRequest;
|
||||
}
|
||||
|
||||
throw new BadDataException("Pull Request Number not found");
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { GetLlmType } from "../../Config";
|
||||
import LlmType from "../../Types/LlmType";
|
||||
import LlmBase, { CopilotPromptResult } from "./LLMBase";
|
||||
import LLMServer from "./LLMServer";
|
||||
|
||||
import OpenAI from "./OpenAI";
|
||||
import { CopilotActionPrompt } from "../CopilotActions/Types";
|
||||
|
||||
export default class LLM extends LlmBase {
|
||||
public static override async getResponse(
|
||||
data: CopilotActionPrompt,
|
||||
): Promise<CopilotPromptResult> {
|
||||
if (GetLlmType() === LlmType.ONEUPTIME_LLM) {
|
||||
return await LLMServer.getResponse(data);
|
||||
}
|
||||
|
||||
if (GetLlmType() === LlmType.OpenAI) {
|
||||
return await OpenAI.getResponse(data);
|
||||
}
|
||||
|
||||
throw new BadDataException("Invalid LLM type");
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import NotImplementedException from "Common/Types/Exception/NotImplementedException";
|
||||
import { JSONValue } from "Common/Types/JSON";
|
||||
import { CopilotActionPrompt } from "../CopilotActions/Types";
|
||||
|
||||
export interface CopilotPromptResult {
|
||||
output: JSONValue;
|
||||
}
|
||||
|
||||
export default class LlmBase {
|
||||
public static async getResponse(
|
||||
_data: CopilotActionPrompt,
|
||||
): Promise<CopilotPromptResult> {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
import URL from "Common/Types/API/URL";
|
||||
import { GetLlmServerUrl } from "../../Config";
|
||||
import LlmBase, { CopilotPromptResult } from "./LLMBase";
|
||||
import API from "Common/Utils/API";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import { JSONArray, JSONObject } from "Common/Types/JSON";
|
||||
import BadRequestException from "Common/Types/Exception/BadRequestException";
|
||||
import Sleep from "Common/Types/Sleep";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import ErrorGettingResponseFromLLM from "../../Exceptions/ErrorGettingResponseFromLLM";
|
||||
import BadOperationException from "Common/Types/Exception/BadOperationException";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import LLMTimeoutException from "../../Exceptions/LLMTimeoutException";
|
||||
import { CopilotActionPrompt } from "../CopilotActions/Types";
|
||||
import { Prompt } from "./Prompt";
|
||||
|
||||
enum LlamaPromptStatus {
|
||||
Processed = "processed",
|
||||
NotFound = "not found",
|
||||
Pending = "pending",
|
||||
}
|
||||
|
||||
export default class Llama extends LlmBase {
|
||||
public static override async getResponse(
|
||||
data: CopilotActionPrompt,
|
||||
): Promise<CopilotPromptResult> {
|
||||
const serverUrl: URL = GetLlmServerUrl();
|
||||
|
||||
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.post(
|
||||
URL.fromString(serverUrl.toString()).addRoute("/prompt/"),
|
||||
{
|
||||
messages: data.messages.map((message: Prompt) => {
|
||||
return {
|
||||
content: message.content,
|
||||
role: message.role,
|
||||
};
|
||||
}),
|
||||
// secretkey: GetRepositorySecretKey(),
|
||||
},
|
||||
{},
|
||||
{
|
||||
retries: 3,
|
||||
exponentialBackoff: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (response instanceof HTTPErrorResponse) {
|
||||
throw response;
|
||||
}
|
||||
|
||||
const result: JSONObject = response.data;
|
||||
|
||||
const idOfPrompt: string = result["id"] as string;
|
||||
|
||||
if (result["error"] && typeof result["error"] === "string") {
|
||||
throw new BadOperationException(result["error"]);
|
||||
}
|
||||
|
||||
// now check this prompt status.
|
||||
|
||||
let promptStatus: LlamaPromptStatus = LlamaPromptStatus.Pending;
|
||||
let promptResult: JSONObject | null = null;
|
||||
|
||||
const currentDate: Date = OneUptimeDate.getCurrentDate();
|
||||
const timeoutInMinutes: number = data.timeoutInMinutes || 5;
|
||||
|
||||
while (promptStatus === LlamaPromptStatus.Pending) {
|
||||
const timeNow: Date = OneUptimeDate.getCurrentDate();
|
||||
|
||||
if (
|
||||
OneUptimeDate.getDifferenceInMinutes(timeNow, currentDate) >
|
||||
timeoutInMinutes
|
||||
) {
|
||||
throw new LLMTimeoutException(
|
||||
`Timeout of ${timeoutInMinutes} minutes exceeded. Skipping the prompt.`,
|
||||
);
|
||||
}
|
||||
|
||||
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.post(
|
||||
URL.fromString(serverUrl.toString()).addRoute(`/prompt-result/`),
|
||||
{
|
||||
id: idOfPrompt,
|
||||
// secretkey: GetRepositorySecretKey(),
|
||||
},
|
||||
{},
|
||||
{
|
||||
retries: 3,
|
||||
exponentialBackoff: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (response instanceof HTTPErrorResponse) {
|
||||
throw response;
|
||||
}
|
||||
|
||||
if (
|
||||
response.data["error"] &&
|
||||
typeof response.data["error"] === "string"
|
||||
) {
|
||||
throw new BadOperationException(response.data["error"]);
|
||||
}
|
||||
|
||||
const result: JSONObject = response.data;
|
||||
|
||||
promptStatus = result["status"] as LlamaPromptStatus;
|
||||
|
||||
if (promptStatus === LlamaPromptStatus.Processed) {
|
||||
logger.debug("Prompt is processed");
|
||||
promptResult = result;
|
||||
} else if (promptStatus === LlamaPromptStatus.NotFound) {
|
||||
throw new ErrorGettingResponseFromLLM("Error processing prompt");
|
||||
} else if (promptStatus === LlamaPromptStatus.Pending) {
|
||||
logger.debug("Prompt is still pending. Waiting for 1 second");
|
||||
await Sleep.sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (!promptResult) {
|
||||
throw new BadRequestException("Failed to get response from Llama server");
|
||||
}
|
||||
|
||||
if (
|
||||
promptResult["output"] &&
|
||||
(promptResult["output"] as JSONArray).length > 0
|
||||
) {
|
||||
promptResult = (promptResult["output"] as JSONArray)[0] as JSONObject;
|
||||
}
|
||||
|
||||
if (promptResult && (promptResult as JSONObject)["generated_text"]) {
|
||||
const arrayOfGeneratedText: JSONArray = (promptResult as JSONObject)[
|
||||
"generated_text"
|
||||
] as JSONArray;
|
||||
|
||||
// get last item
|
||||
|
||||
const lastItem: JSONObject = arrayOfGeneratedText[
|
||||
arrayOfGeneratedText.length - 1
|
||||
] as JSONObject;
|
||||
|
||||
if (lastItem["content"]) {
|
||||
return {
|
||||
output: lastItem["content"] as string,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw new BadRequestException("Failed to get response from Llama server");
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import OpenAI from "openai";
|
||||
import { GetOpenAIAPIKey, GetOpenAIModel } from "../../Config";
|
||||
import LlmBase, { CopilotPromptResult } from "./LLMBase";
|
||||
import BadRequestException from "Common/Types/Exception/BadRequestException";
|
||||
import { CopilotActionPrompt } from "../CopilotActions/Types";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
|
||||
export default class Llama extends LlmBase {
|
||||
public static openai: OpenAI | null = null;
|
||||
|
||||
public static override async getResponse(
|
||||
data: CopilotActionPrompt,
|
||||
): Promise<CopilotPromptResult> {
|
||||
if (!GetOpenAIAPIKey() || !GetOpenAIModel()) {
|
||||
throw new BadRequestException("OpenAI API Key or Model is not set");
|
||||
}
|
||||
|
||||
if (!this.openai) {
|
||||
this.openai = new OpenAI({
|
||||
apiKey: GetOpenAIAPIKey() as string,
|
||||
});
|
||||
}
|
||||
|
||||
logger.debug("Getting response from OpenAI");
|
||||
|
||||
const chatCompletion: OpenAI.Chat.Completions.ChatCompletion =
|
||||
await this.openai.chat.completions.create({
|
||||
messages: data.messages,
|
||||
model: GetOpenAIModel()!,
|
||||
});
|
||||
|
||||
logger.debug("Got response from OpenAI");
|
||||
|
||||
if (
|
||||
chatCompletion.choices.length > 0 &&
|
||||
chatCompletion.choices[0]?.message?.content
|
||||
) {
|
||||
const response: string = chatCompletion.choices[0]!.message.content;
|
||||
|
||||
logger.debug(`Response from OpenAI: ${response}`);
|
||||
|
||||
return {
|
||||
output: response,
|
||||
};
|
||||
}
|
||||
|
||||
throw new BadRequestException("Failed to get response from OpenAI server");
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export enum PromptRole {
|
||||
System = "system",
|
||||
User = "user",
|
||||
Assistant = "assistant",
|
||||
}
|
||||
|
||||
export interface Prompt {
|
||||
content: string;
|
||||
role: PromptRole;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
## OneUptime Copilot
|
||||
|
||||
This folder contains the configuration files for the OneUptime Copilot. The Copilot is a tool that automatically improves your code. It can fix issues, improve code quality, and help you ship faster.
|
||||
|
||||
This folder has the following structure:
|
||||
|
||||
- `config.js`: The configuration file for the Copilot. You can customize the Copilot's behavior by changing this file.
|
||||
- `scripts`: A folder containing scripts that the Copilot runs. These are hooks that run at different stages of the Copilot's process.
|
||||
- `on-after-clone.sh`: A script that runs after the Copilot clones your repository.
|
||||
- `on-before-code-change.sh`: A script that runs before the Copilot makes changes to your code.
|
||||
- `on-after-code-change.sh`: A script that runs after the Copilot makes changes to your code.
|
||||
- `on-before-commit.sh`: A script that runs before the Copilot commits changes to your repository.
|
||||
- `on-after-commit.sh`: A script that runs after the Copilot commits changes to your repository.
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// This is the configuration file for the oneuptime copilot.
|
||||
|
||||
const getCopilotConfig = () => {
|
||||
return {
|
||||
// The version of the schema for this configuration file.
|
||||
schemaVersion: '1.0',
|
||||
}
|
||||
}
|
||||
|
||||
export default getCopilotConfig;
|
||||
@@ -1,13 +0,0 @@
|
||||
# Description: Copilot clones your repository and to improve your code.
|
||||
# This scirpt runs after the clone process is completed.
|
||||
# Some of the common tasks you can do here are:
|
||||
# 1. Install dependencies
|
||||
# 2. Run linting
|
||||
# 3. Run tests
|
||||
# 4. Run build
|
||||
# 5. Run any other command that you want to run after the clone process is completed.
|
||||
# If this script fails, copilot will not proceed with the next steps to improve your code.
|
||||
# This step is to ensure that the code is in a good state before we start improving it.
|
||||
# If you want to skip this script, you can keep this file empty.
|
||||
# It's highly recommended to run linting and tests in this script to ensure the code is in a good state.
|
||||
# This scirpt will run on ubuntu machine. So, make sure the commands you run are compatible with ubuntu.
|
||||
@@ -1,11 +0,0 @@
|
||||
# Description: Copilot will run this script after we make improvements to your code and write it to disk.
|
||||
# Some of the common tasks you can do here are:
|
||||
# 1. Run linting
|
||||
# 2. Run tests
|
||||
# 3. Run build
|
||||
# 4. Run any other command that you want to run after the code is changed.
|
||||
# If this script fails, copilot will not commit the changes to your repository.
|
||||
# This step is to ensure that the code is in a good state before we commit the changes.
|
||||
# If you want to skip this script, you can keep this file empty.
|
||||
# It's highly recommended to run linting and tests in this script to ensure the code is in a good state.
|
||||
# This scirpt will run on ubuntu machine. So, make sure the commands you run are compatible with ubuntu.
|
||||
@@ -1 +0,0 @@
|
||||
# Description: Copilot will run this script after the commit process is completed.
|
||||
@@ -1,9 +0,0 @@
|
||||
# Description: Copilot will run this script before we make changes to your code.
|
||||
# Some of the common tasks you can do here are:
|
||||
# 1. Install dependencies
|
||||
# 2. Run any other command that you want to run before the code is changed.
|
||||
# If this script fails, copilot will not make any changes to the code.
|
||||
# This step is to ensure that the code is in a good state before we start making changes.
|
||||
# If you want to skip this script, you can keep this file empty.
|
||||
# It's highly recommended to run things like installing dependencies in this script.
|
||||
# This scirpt will run on ubuntu machine. So, make sure the commands you run are compatible with ubuntu.
|
||||
@@ -1 +0,0 @@
|
||||
# Description: Copilot will run this script before we commit the changes to your repository.
|
||||
@@ -1,6 +0,0 @@
|
||||
enum LlmType {
|
||||
ONEUPTIME_LLM = "OneUptime LLM Server", // OneUptime custom LLM Server
|
||||
OpenAI = "OpenAI",
|
||||
}
|
||||
|
||||
export default LlmType;
|
||||
@@ -1,820 +0,0 @@
|
||||
import {
|
||||
GetCodeRepositoryPassword,
|
||||
GetCodeRepositoryUsername,
|
||||
GetLocalRepositoryPath,
|
||||
GetOneUptimeURL,
|
||||
GetRepositorySecretKey,
|
||||
} from "../Config";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import CodeRepositoryType from "Common/Types/CodeRepository/CodeRepositoryType";
|
||||
import PullRequest from "Common/Types/CodeRepository/PullRequest";
|
||||
import PullRequestState from "Common/Types/CodeRepository/PullRequestState";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import { JSONArray, JSONObject } from "Common/Types/JSON";
|
||||
import API from "Common/Utils/API";
|
||||
import CodeRepositoryServerUtil from "Common/Server/Utils/CodeRepository/CodeRepository";
|
||||
import GitHubUtil from "Common/Server/Utils/CodeRepository/GitHub/GitHub";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import CopilotCodeRepository from "Common/Models/DatabaseModels/CopilotCodeRepository";
|
||||
import ServiceCopilotCodeRepository from "Common/Models/DatabaseModels/ServiceCopilotCodeRepository";
|
||||
import Text from "Common/Types/Text";
|
||||
import Execute from "Common/Server/Utils/Execute";
|
||||
import CopilotPullRequestService from "../Service/CopilotPullRequest";
|
||||
import CopilotPullRequest from "Common/Models/DatabaseModels/CopilotPullRequest";
|
||||
|
||||
export interface CodeRepositoryResult {
|
||||
codeRepository: CopilotCodeRepository;
|
||||
serviceRepositories: Array<ServiceCopilotCodeRepository>;
|
||||
}
|
||||
|
||||
export interface ServiceToImproveResult {
|
||||
serviceRepository: ServiceCopilotCodeRepository;
|
||||
numberOfOpenPullRequests: number;
|
||||
pullRequests: Array<CopilotPullRequest>;
|
||||
}
|
||||
|
||||
export enum RepoScriptType {
|
||||
OnAfterClone = "onAfterClone",
|
||||
OnBeforeCommit = "onBeforeCommit",
|
||||
OnAfterCommit = "OnAfterCommit",
|
||||
OnBeforeCodeChange = "OnBeforeCodeChange",
|
||||
OnAfterCodeChange = "OnAfterCodeChange",
|
||||
}
|
||||
|
||||
export default class CodeRepositoryUtil {
|
||||
public static codeRepositoryResult: CodeRepositoryResult | null = null;
|
||||
public static gitHubUtil: GitHubUtil | null = null;
|
||||
public static folderNameOfClonedRepository: string | null = null;
|
||||
|
||||
public static async getCurrentCommitHash(): Promise<string> {
|
||||
return await CodeRepositoryServerUtil.getCurrentCommitHash({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
});
|
||||
}
|
||||
|
||||
public static isRepoCloned(): boolean {
|
||||
return Boolean(this.folderNameOfClonedRepository);
|
||||
}
|
||||
|
||||
public static async getOpenSetupPullRequest(): Promise<CopilotPullRequest | null> {
|
||||
const openPullRequests: Array<CopilotPullRequest> =
|
||||
await CopilotPullRequestService.getOpenPullRequestsFromDatabase();
|
||||
|
||||
for (const pullRequest of openPullRequests) {
|
||||
if (pullRequest.isSetupPullRequest) {
|
||||
return pullRequest;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static getLocalRepositoryPath(): string {
|
||||
if (this.folderNameOfClonedRepository) {
|
||||
return LocalFile.sanitizeFilePath(
|
||||
GetLocalRepositoryPath() + "/" + this.folderNameOfClonedRepository,
|
||||
);
|
||||
}
|
||||
|
||||
return GetLocalRepositoryPath();
|
||||
}
|
||||
|
||||
public static async discardAllChangesOnCurrentBranch(): Promise<void> {
|
||||
await CodeRepositoryServerUtil.discardAllChangesOnCurrentBranch({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
});
|
||||
}
|
||||
|
||||
public static async setAuthorIdentity(data: {
|
||||
name: string;
|
||||
email: string;
|
||||
}): Promise<void> {
|
||||
await CodeRepositoryServerUtil.setAuthorIdentity({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
authorName: data.name,
|
||||
authorEmail: data.email,
|
||||
});
|
||||
}
|
||||
|
||||
public static async getPullRequestState(data: {
|
||||
pullRequestId: string;
|
||||
}): Promise<PullRequestState> {
|
||||
// check if org name and repo name is present.
|
||||
|
||||
if (!this.codeRepositoryResult?.codeRepository.organizationName) {
|
||||
throw new BadDataException("Organization Name is required");
|
||||
}
|
||||
|
||||
if (!this.codeRepositoryResult?.codeRepository.repositoryName) {
|
||||
throw new BadDataException("Repository Name is required");
|
||||
}
|
||||
|
||||
const githubUtil: GitHubUtil = this.getGitHubUtil();
|
||||
|
||||
if (!githubUtil) {
|
||||
throw new BadDataException("GitHub Util is required");
|
||||
}
|
||||
|
||||
const pullRequest: PullRequest | undefined =
|
||||
await githubUtil.getPullRequestByNumber({
|
||||
organizationName:
|
||||
this.codeRepositoryResult.codeRepository.organizationName,
|
||||
repositoryName: this.codeRepositoryResult.codeRepository.repositoryName,
|
||||
pullRequestId: data.pullRequestId,
|
||||
});
|
||||
|
||||
if (!pullRequest) {
|
||||
throw new BadDataException("Pull Request not found");
|
||||
}
|
||||
|
||||
return pullRequest.state;
|
||||
}
|
||||
|
||||
public static async setUpRepo(): Promise<PullRequest> {
|
||||
// check if the repository is setup properly.
|
||||
const isRepoSetupProperly: boolean = await this.isRepoSetupProperly();
|
||||
|
||||
if (isRepoSetupProperly) {
|
||||
throw new BadDataException("Repository is already setup properly.");
|
||||
}
|
||||
|
||||
// otherwise, we copy the folder /usr/src/app/Templates/.oneuptime to the repository folder.
|
||||
|
||||
const templateFolderPath: string = LocalFile.sanitizeFilePath(
|
||||
"/usr/src/app/Templates/.oneuptime",
|
||||
);
|
||||
|
||||
const oneUptimeConfigPath: string = LocalFile.sanitizeFilePath(
|
||||
this.getLocalRepositoryPath() + "/.oneuptime",
|
||||
);
|
||||
|
||||
// create a new branch called oneuptime-copilot-setup
|
||||
|
||||
const branchName: string = "setup-" + Text.generateRandomText(5);
|
||||
|
||||
await this.createBranch({
|
||||
branchName: branchName,
|
||||
});
|
||||
|
||||
await LocalFile.makeDirectory(oneUptimeConfigPath);
|
||||
|
||||
await LocalFile.copyDirectory({
|
||||
source: templateFolderPath,
|
||||
destination: oneUptimeConfigPath,
|
||||
});
|
||||
|
||||
// add all the files to the git.
|
||||
|
||||
await this.addAllChangedFilesToGit();
|
||||
|
||||
// commit the changes.
|
||||
|
||||
await this.commitChanges({
|
||||
message: "OneUptime Copilot Setup",
|
||||
});
|
||||
|
||||
// push changes to the repo.
|
||||
|
||||
await this.pushChanges({
|
||||
branchName: branchName,
|
||||
});
|
||||
|
||||
// create a pull request.
|
||||
|
||||
const pullRequest: PullRequest = await this.createPullRequest({
|
||||
branchName: branchName,
|
||||
title: "OneUptime Copilot Setup",
|
||||
body: "This pull request is created by OneUptime Copilot to setup the repository.",
|
||||
});
|
||||
|
||||
// save this to the database.
|
||||
|
||||
await CopilotPullRequestService.addPullRequestToDatabase({
|
||||
pullRequest: pullRequest,
|
||||
isSetupPullRequest: true,
|
||||
});
|
||||
|
||||
return pullRequest;
|
||||
}
|
||||
|
||||
public static async isRepoSetupProperly(): Promise<boolean> {
|
||||
// check if .oneuptime folder exists.
|
||||
|
||||
const repoPath: string = this.getLocalRepositoryPath();
|
||||
|
||||
const oneUptimeFolderPath: string = LocalFile.sanitizeFilePath(
|
||||
`${repoPath}/.oneuptime`,
|
||||
);
|
||||
|
||||
const doesDirectoryExist: boolean =
|
||||
await LocalFile.doesDirectoryExist(oneUptimeFolderPath);
|
||||
|
||||
if (!doesDirectoryExist) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if .oneuptime/scripts folder exists.
|
||||
|
||||
const oneuptimeScriptsPath: string = LocalFile.sanitizeFilePath(
|
||||
`${oneUptimeFolderPath}/scripts`,
|
||||
);
|
||||
|
||||
const doesScriptsDirectoryExist: boolean =
|
||||
await LocalFile.doesDirectoryExist(oneuptimeScriptsPath);
|
||||
|
||||
if (!doesScriptsDirectoryExist) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // return true if all checks pass.
|
||||
}
|
||||
|
||||
public static addAllChangedFilesToGit(): Promise<void> {
|
||||
return CodeRepositoryServerUtil.addAllChangedFilesToGit({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
});
|
||||
}
|
||||
|
||||
// returns the folder name of the cloned repository.
|
||||
public static async cloneRepository(data: {
|
||||
codeRepository: CopilotCodeRepository;
|
||||
}): Promise<void> {
|
||||
// make sure this.getLocalRepositoryPath() is empty.
|
||||
const repoLocalPath: string = this.getLocalRepositoryPath();
|
||||
|
||||
await LocalFile.deleteAllDataInDirectory(repoLocalPath);
|
||||
await LocalFile.makeDirectory(repoLocalPath);
|
||||
|
||||
// check if the data in the directory eixsts, if it does then delete it.
|
||||
|
||||
if (!data.codeRepository.repositoryHostedAt) {
|
||||
throw new BadDataException("Repository Hosted At is required");
|
||||
}
|
||||
|
||||
if (!data.codeRepository.mainBranchName) {
|
||||
throw new BadDataException("Main Branch Name is required");
|
||||
}
|
||||
|
||||
if (!data.codeRepository.organizationName) {
|
||||
throw new BadDataException("Organization Name is required");
|
||||
}
|
||||
|
||||
if (!data.codeRepository.repositoryName) {
|
||||
throw new BadDataException("Repository Name is required");
|
||||
}
|
||||
|
||||
const CodeRepositoryUsername: string | null = GetCodeRepositoryUsername();
|
||||
|
||||
if (!CodeRepositoryUsername) {
|
||||
throw new BadDataException("Code Repository Username is required");
|
||||
}
|
||||
|
||||
const CodeRepositoryPassword: string | null = GetCodeRepositoryPassword();
|
||||
|
||||
if (!CodeRepositoryPassword) {
|
||||
throw new BadDataException("Code Repository Password is required");
|
||||
}
|
||||
|
||||
const repoUrl: string = `https://${CodeRepositoryUsername}:${CodeRepositoryPassword}@${
|
||||
data.codeRepository.repositoryHostedAt === CodeRepositoryType.GitHub
|
||||
? "github.com"
|
||||
: ""
|
||||
}/${data.codeRepository.organizationName}/${data.codeRepository.repositoryName}.git`;
|
||||
|
||||
const folderName: string = await CodeRepositoryServerUtil.cloneRepository({
|
||||
repoUrl: repoUrl,
|
||||
repoPath: repoLocalPath,
|
||||
});
|
||||
|
||||
this.folderNameOfClonedRepository = folderName;
|
||||
|
||||
logger.debug(`Repository cloned to ${repoLocalPath}/${folderName}`);
|
||||
}
|
||||
|
||||
public static async executeScript(data: { script: string }): Promise<string> {
|
||||
const commands: Array<string> = data.script
|
||||
.split("\n")
|
||||
.filter((command: string) => {
|
||||
return command.trim() !== "" && !command.startsWith("#");
|
||||
});
|
||||
|
||||
const results: Array<string> = [];
|
||||
|
||||
for (const command of commands) {
|
||||
logger.info(`Executing command: ${command}`);
|
||||
const commandResult: string = await Execute.executeCommand(
|
||||
`cd ${this.getLocalRepositoryPath()} && ${command}`,
|
||||
);
|
||||
if (commandResult) {
|
||||
logger.info(`Command result: ${commandResult}`);
|
||||
results.push(commandResult);
|
||||
}
|
||||
}
|
||||
|
||||
return results.join("\n");
|
||||
}
|
||||
|
||||
public static async getRepoScript(data: {
|
||||
scriptType: RepoScriptType;
|
||||
}): Promise<string | null> {
|
||||
const repoPath: string = this.getLocalRepositoryPath();
|
||||
|
||||
const oneUptimeFolderPath: string = LocalFile.sanitizeFilePath(
|
||||
`${repoPath}/.oneuptime`,
|
||||
);
|
||||
|
||||
const doesDirectoryExist: boolean =
|
||||
await LocalFile.doesDirectoryExist(oneUptimeFolderPath);
|
||||
|
||||
if (!doesDirectoryExist) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const oneuptimeScriptsPath: string = LocalFile.sanitizeFilePath(
|
||||
`${oneUptimeFolderPath}/scripts`,
|
||||
);
|
||||
|
||||
const doesScriptsDirectoryExist: boolean =
|
||||
await LocalFile.doesDirectoryExist(oneuptimeScriptsPath);
|
||||
|
||||
if (!doesScriptsDirectoryExist) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const scriptPath: string = LocalFile.sanitizeFilePath(
|
||||
`${oneuptimeScriptsPath}/${Text.fromPascalCaseToDashes(data.scriptType)}.sh`,
|
||||
);
|
||||
|
||||
const doesScriptExist: boolean = await LocalFile.doesFileExist(scriptPath);
|
||||
|
||||
if (!doesScriptExist) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const scriptContent: string = await LocalFile.read(scriptPath);
|
||||
|
||||
return scriptContent.trim() || null;
|
||||
}
|
||||
|
||||
public static hasOpenPRForFile(data: {
|
||||
filePath: string;
|
||||
pullRequests: Array<PullRequest>;
|
||||
}): boolean {
|
||||
const pullRequests: Array<PullRequest> = this.getOpenPRForFile(data);
|
||||
return pullRequests.length > 0;
|
||||
}
|
||||
|
||||
public static getOpenPRForFile(data: {
|
||||
filePath: string;
|
||||
pullRequests: Array<PullRequest>;
|
||||
}): Array<PullRequest> {
|
||||
const pullRequests: Array<PullRequest> = [];
|
||||
|
||||
for (const pullRequest of data.pullRequests) {
|
||||
if (pullRequest.title.includes(data.filePath)) {
|
||||
pullRequests.push(pullRequest);
|
||||
}
|
||||
}
|
||||
|
||||
return pullRequests;
|
||||
}
|
||||
|
||||
public static async listFilesInDirectory(data: {
|
||||
directoryPath: string;
|
||||
}): Promise<Array<string>> {
|
||||
return await CodeRepositoryServerUtil.listFilesInDirectory({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
directoryPath: data.directoryPath,
|
||||
});
|
||||
}
|
||||
|
||||
public static getGitHubUtil(): GitHubUtil {
|
||||
if (!this.gitHubUtil) {
|
||||
const gitHubToken: string | null = GetCodeRepositoryPassword();
|
||||
|
||||
const gitHubUsername: string | null = GetCodeRepositoryUsername();
|
||||
|
||||
if (!gitHubUsername) {
|
||||
throw new BadDataException("GitHub Username is required");
|
||||
}
|
||||
|
||||
if (!gitHubToken) {
|
||||
throw new BadDataException("GitHub Token is required");
|
||||
}
|
||||
|
||||
this.gitHubUtil = new GitHubUtil({
|
||||
authToken: gitHubToken,
|
||||
username: gitHubUsername!,
|
||||
});
|
||||
}
|
||||
|
||||
return this.gitHubUtil;
|
||||
}
|
||||
|
||||
public static async pullChanges(): Promise<void> {
|
||||
await CodeRepositoryServerUtil.pullChanges({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
});
|
||||
}
|
||||
|
||||
public static getBranchName(data: { branchName: string }): string {
|
||||
return "oneuptime-copilot-" + data.branchName;
|
||||
}
|
||||
|
||||
public static async createBranch(data: {
|
||||
branchName: string;
|
||||
}): Promise<void> {
|
||||
const branchName: string = this.getBranchName({
|
||||
branchName: data.branchName,
|
||||
});
|
||||
|
||||
await CodeRepositoryServerUtil.createBranch({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
branchName: branchName,
|
||||
});
|
||||
}
|
||||
|
||||
public static async createOrCheckoutBranch(data: {
|
||||
branchName: string;
|
||||
}): Promise<void> {
|
||||
const branchName: string = this.getBranchName({
|
||||
branchName: data.branchName,
|
||||
});
|
||||
|
||||
await CodeRepositoryServerUtil.createOrCheckoutBranch({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
branchName: branchName,
|
||||
});
|
||||
}
|
||||
|
||||
public static async writeToFile(data: {
|
||||
filePath: string;
|
||||
content: string;
|
||||
}): Promise<void> {
|
||||
await CodeRepositoryServerUtil.writeToFile({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
filePath: data.filePath,
|
||||
content: data.content,
|
||||
});
|
||||
}
|
||||
|
||||
public static async createDirectory(data: {
|
||||
directoryPath: string;
|
||||
}): Promise<void> {
|
||||
await CodeRepositoryServerUtil.createDirectory({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
directoryPath: data.directoryPath,
|
||||
});
|
||||
}
|
||||
|
||||
public static async deleteFile(data: { filePath: string }): Promise<void> {
|
||||
await CodeRepositoryServerUtil.deleteFile({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
filePath: data.filePath,
|
||||
});
|
||||
}
|
||||
|
||||
public static async deleteDirectory(data: {
|
||||
directoryPath: string;
|
||||
}): Promise<void> {
|
||||
await CodeRepositoryServerUtil.deleteDirectory({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
directoryPath: data.directoryPath,
|
||||
});
|
||||
}
|
||||
|
||||
public static async discardChanges(): Promise<void> {
|
||||
if (this.isRepoCloned()) {
|
||||
await CodeRepositoryServerUtil.discardChanges({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static async checkoutBranch(data: {
|
||||
branchName: string;
|
||||
}): Promise<void> {
|
||||
if (this.isRepoCloned()) {
|
||||
await CodeRepositoryServerUtil.checkoutBranch({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
branchName: data.branchName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static async checkoutMainBranch(): Promise<void> {
|
||||
if (!this.isRepoCloned()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const codeRepository: CopilotCodeRepository =
|
||||
await this.getCodeRepository();
|
||||
|
||||
if (!codeRepository.mainBranchName) {
|
||||
throw new BadDataException("Main Branch Name is required");
|
||||
}
|
||||
|
||||
await this.checkoutBranch({
|
||||
branchName: codeRepository.mainBranchName!,
|
||||
});
|
||||
}
|
||||
|
||||
public static async addFilesToGit(data: {
|
||||
filePaths: Array<string>;
|
||||
}): Promise<void> {
|
||||
await CodeRepositoryServerUtil.addFilesToGit({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
filePaths: data.filePaths,
|
||||
});
|
||||
}
|
||||
|
||||
public static async commitChanges(data: { message: string }): Promise<void> {
|
||||
let username: string | null = null;
|
||||
|
||||
if (
|
||||
this.codeRepositoryResult?.codeRepository.repositoryHostedAt ===
|
||||
CodeRepositoryType.GitHub
|
||||
) {
|
||||
username = GetCodeRepositoryUsername();
|
||||
}
|
||||
|
||||
if (!username) {
|
||||
throw new BadDataException("Username is required");
|
||||
}
|
||||
|
||||
await CodeRepositoryServerUtil.commitChanges({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
message: data.message,
|
||||
});
|
||||
}
|
||||
|
||||
public static async pushChanges(data: { branchName: string }): Promise<void> {
|
||||
const branchName: string = this.getBranchName({
|
||||
branchName: data.branchName,
|
||||
});
|
||||
|
||||
const codeRepository: CopilotCodeRepository =
|
||||
await this.getCodeRepository();
|
||||
|
||||
if (!codeRepository.mainBranchName) {
|
||||
throw new BadDataException("Main Branch Name is required");
|
||||
}
|
||||
|
||||
if (!codeRepository.organizationName) {
|
||||
throw new BadDataException("Organization Name is required");
|
||||
}
|
||||
|
||||
if (!codeRepository.repositoryName) {
|
||||
throw new BadDataException("Repository Name is required");
|
||||
}
|
||||
|
||||
if (codeRepository.repositoryHostedAt === CodeRepositoryType.GitHub) {
|
||||
return await this.getGitHubUtil().pushChanges({
|
||||
repoPath: this.getLocalRepositoryPath(),
|
||||
branchName: branchName,
|
||||
organizationName: codeRepository.organizationName,
|
||||
repositoryName: codeRepository.repositoryName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static async switchToMainBranch(): Promise<void> {
|
||||
const codeRepository: CopilotCodeRepository =
|
||||
await this.getCodeRepository();
|
||||
|
||||
if (!codeRepository.mainBranchName) {
|
||||
throw new BadDataException("Main Branch Name is required");
|
||||
}
|
||||
|
||||
await this.checkoutBranch({
|
||||
branchName: codeRepository.mainBranchName!,
|
||||
});
|
||||
}
|
||||
|
||||
public static async createPullRequest(data: {
|
||||
branchName: string;
|
||||
title: string;
|
||||
body: string;
|
||||
}): Promise<PullRequest> {
|
||||
const branchName: string = this.getBranchName({
|
||||
branchName: data.branchName,
|
||||
});
|
||||
|
||||
const codeRepository: CopilotCodeRepository =
|
||||
await this.getCodeRepository();
|
||||
|
||||
if (!codeRepository.mainBranchName) {
|
||||
throw new BadDataException("Main Branch Name is required");
|
||||
}
|
||||
|
||||
if (!codeRepository.organizationName) {
|
||||
throw new BadDataException("Organization Name is required");
|
||||
}
|
||||
|
||||
if (!codeRepository.repositoryName) {
|
||||
throw new BadDataException("Repository Name is required");
|
||||
}
|
||||
|
||||
if (codeRepository.repositoryHostedAt === CodeRepositoryType.GitHub) {
|
||||
return await this.getGitHubUtil().createPullRequest({
|
||||
headBranchName: branchName,
|
||||
baseBranchName: codeRepository.mainBranchName,
|
||||
organizationName: codeRepository.organizationName,
|
||||
repositoryName: codeRepository.repositoryName,
|
||||
title: data.title,
|
||||
body: data.body,
|
||||
});
|
||||
}
|
||||
throw new BadDataException("Code Repository type not supported");
|
||||
}
|
||||
|
||||
public static async getServicesToImproveCode(data: {
|
||||
codeRepository: CopilotCodeRepository;
|
||||
serviceRepositories: Array<ServiceCopilotCodeRepository>;
|
||||
openPullRequests: Array<CopilotPullRequest>;
|
||||
}): Promise<Array<ServiceToImproveResult>> {
|
||||
const servicesToImproveCode: Array<ServiceToImproveResult> = [];
|
||||
|
||||
for (const service of data.serviceRepositories) {
|
||||
if (!data.codeRepository.mainBranchName) {
|
||||
throw new BadDataException("Main Branch Name is required");
|
||||
}
|
||||
|
||||
if (!data.codeRepository.organizationName) {
|
||||
throw new BadDataException("Organization Name is required");
|
||||
}
|
||||
|
||||
if (!data.codeRepository.repositoryName) {
|
||||
throw new BadDataException("Repository Name is required");
|
||||
}
|
||||
|
||||
if (!service.limitNumberOfOpenPullRequestsCount) {
|
||||
throw new BadDataException(
|
||||
"Limit Number Of Open Pull Requests Count is required",
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
data.codeRepository.repositoryHostedAt === CodeRepositoryType.GitHub
|
||||
) {
|
||||
const gitHuhbToken: string | null = GetCodeRepositoryPassword();
|
||||
|
||||
if (!gitHuhbToken) {
|
||||
throw new BadDataException("GitHub Token is required");
|
||||
}
|
||||
|
||||
const pullRequestByService: Array<CopilotPullRequest> =
|
||||
data.openPullRequests.filter((pullRequest: CopilotPullRequest) => {
|
||||
return (
|
||||
pullRequest.serviceRepositoryId?.toString() ===
|
||||
service.id?.toString()
|
||||
);
|
||||
});
|
||||
|
||||
const numberOfPullRequestForThisService: number =
|
||||
pullRequestByService.length;
|
||||
|
||||
if (
|
||||
numberOfPullRequestForThisService <
|
||||
service.limitNumberOfOpenPullRequestsCount
|
||||
) {
|
||||
servicesToImproveCode.push({
|
||||
serviceRepository: service,
|
||||
numberOfOpenPullRequests: numberOfPullRequestForThisService,
|
||||
pullRequests: pullRequestByService,
|
||||
});
|
||||
logger.info(
|
||||
`Service ${service.serviceCatalog?.name} has ${numberOfPullRequestForThisService} open pull requests. Limit is ${service.limitNumberOfOpenPullRequestsCount}. Adding to the list to improve code...`,
|
||||
);
|
||||
} else {
|
||||
logger.warn(
|
||||
`Service ${service.serviceCatalog?.name} has ${numberOfPullRequestForThisService} open pull requests. Limit is ${service.limitNumberOfOpenPullRequestsCount}. Skipping...`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return servicesToImproveCode;
|
||||
}
|
||||
|
||||
public static async getCodeRepositoryResult(): Promise<CodeRepositoryResult> {
|
||||
if (this.codeRepositoryResult) {
|
||||
return this.codeRepositoryResult;
|
||||
}
|
||||
|
||||
logger.info("Fetching Code Repository...");
|
||||
|
||||
const repositorySecretKey: string | null = GetRepositorySecretKey();
|
||||
|
||||
if (!repositorySecretKey) {
|
||||
throw new BadDataException("Repository Secret Key is required");
|
||||
}
|
||||
|
||||
const url: URL = URL.fromString(
|
||||
GetOneUptimeURL().toString() + "/api",
|
||||
).addRoute(
|
||||
`${new CopilotCodeRepository()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/get-code-repository/${repositorySecretKey}`,
|
||||
);
|
||||
|
||||
const codeRepositoryResult: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.get(url);
|
||||
|
||||
if (codeRepositoryResult instanceof HTTPErrorResponse) {
|
||||
throw codeRepositoryResult;
|
||||
}
|
||||
|
||||
const codeRepository: CopilotCodeRepository =
|
||||
CopilotCodeRepository.fromJSON(
|
||||
codeRepositoryResult.data["codeRepository"] as JSONObject,
|
||||
CopilotCodeRepository,
|
||||
) as CopilotCodeRepository;
|
||||
|
||||
const servicesRepository: Array<ServiceCopilotCodeRepository> = (
|
||||
codeRepositoryResult.data["servicesRepository"] as JSONArray
|
||||
).map((serviceRepository: JSONObject) => {
|
||||
return ServiceCopilotCodeRepository.fromJSON(
|
||||
serviceRepository,
|
||||
ServiceCopilotCodeRepository,
|
||||
) as ServiceCopilotCodeRepository;
|
||||
});
|
||||
|
||||
if (!codeRepository) {
|
||||
throw new BadDataException(
|
||||
"Code Repository not found with the secret key provided.",
|
||||
);
|
||||
}
|
||||
|
||||
if (!servicesRepository || servicesRepository.length === 0) {
|
||||
throw new BadDataException(
|
||||
"No services attached to this repository. Please attach services to this repository on OneUptime Dashboard.",
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Code Repository found: ${codeRepository.name}`);
|
||||
|
||||
logger.info("Services found in the repository:");
|
||||
|
||||
servicesRepository.forEach(
|
||||
(serviceRepository: ServiceCopilotCodeRepository) => {
|
||||
logger.info(`- ${serviceRepository.serviceCatalog?.name}`);
|
||||
},
|
||||
);
|
||||
|
||||
this.codeRepositoryResult = {
|
||||
codeRepository,
|
||||
serviceRepositories: servicesRepository,
|
||||
};
|
||||
|
||||
return this.codeRepositoryResult;
|
||||
}
|
||||
|
||||
public static async getCodeRepository(): Promise<CopilotCodeRepository> {
|
||||
if (!this.codeRepositoryResult) {
|
||||
const result: CodeRepositoryResult = await this.getCodeRepositoryResult();
|
||||
return result.codeRepository;
|
||||
}
|
||||
|
||||
return this.codeRepositoryResult.codeRepository;
|
||||
}
|
||||
|
||||
public static getCodeFileExtentions(): Array<string> {
|
||||
const extensions: Array<string> = [
|
||||
"ts",
|
||||
"js",
|
||||
"tsx",
|
||||
"jsx",
|
||||
"py",
|
||||
"go",
|
||||
"java",
|
||||
"c",
|
||||
"cpp",
|
||||
"cs",
|
||||
"swift",
|
||||
"php",
|
||||
"rb",
|
||||
"rs",
|
||||
"kt",
|
||||
"dart",
|
||||
"sh",
|
||||
"pl",
|
||||
"lua",
|
||||
"r",
|
||||
"scala",
|
||||
"ts",
|
||||
"js",
|
||||
"tsx",
|
||||
"jsx",
|
||||
];
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
public static getReadmeFileExtentions(): Array<string> {
|
||||
return ["md"];
|
||||
}
|
||||
}
|
||||
@@ -1,359 +0,0 @@
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import CopilotAction from "Common/Models/DatabaseModels/CopilotAction";
|
||||
import {
|
||||
GetOneUptimeURL,
|
||||
GetRepositorySecretKey,
|
||||
MIN_ITEMS_IN_QUEUE_PER_SERVICE_CATALOG,
|
||||
} from "../Config";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import { JSONArray, JSONObject } from "Common/Types/JSON";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import API from "Common/Utils/API";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import CopilotActionTypePriority from "Common/Models/DatabaseModels/CopilotActionTypePriority";
|
||||
import CopilotActionTypeUtil from "./CopilotActionTypes";
|
||||
import CopilotActionType from "Common/Types/Copilot/CopilotActionType";
|
||||
import { ActionDictionary } from "../Service/CopilotActions/Index";
|
||||
import CopilotActionBase from "../Service/CopilotActions/CopilotActionsBase";
|
||||
import CopilotActionStatus from "Common/Types/Copilot/CopilotActionStatus";
|
||||
import CopilotActionProp from "Common/Types/Copilot/CopilotActionProps/Index";
|
||||
import CodeRepositoryUtil from "./CodeRepository";
|
||||
|
||||
export default class CopilotActionUtil {
|
||||
public static async getExistingAction(data: {
|
||||
serviceCatalogId: ObjectID;
|
||||
actionType: CopilotActionType;
|
||||
actionProps: JSONObject;
|
||||
}): Promise<CopilotAction | null> {
|
||||
if (!data.serviceCatalogId) {
|
||||
throw new BadDataException("Service Catalog ID is required");
|
||||
}
|
||||
|
||||
if (!data.actionType) {
|
||||
throw new BadDataException("Action Type is required");
|
||||
}
|
||||
|
||||
const repositorySecretKey: string | null = GetRepositorySecretKey();
|
||||
|
||||
if (!repositorySecretKey) {
|
||||
throw new BadDataException("Repository Secret Key is required");
|
||||
}
|
||||
|
||||
const url: URL = URL.fromString(
|
||||
GetOneUptimeURL().toString() + "/api",
|
||||
).addRoute(
|
||||
`${new CopilotAction()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/get-copilot-action/${repositorySecretKey}`,
|
||||
);
|
||||
|
||||
const copilotActionResult: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.get(url, {
|
||||
serviceCatalogId: data.serviceCatalogId.toString(),
|
||||
actionType: data.actionType,
|
||||
actionProps: JSON.stringify(data.actionProps),
|
||||
});
|
||||
|
||||
if (copilotActionResult instanceof HTTPErrorResponse) {
|
||||
throw copilotActionResult;
|
||||
}
|
||||
|
||||
if (!copilotActionResult.data["copilotAction"]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return CopilotAction.fromJSONObject(
|
||||
copilotActionResult.data["copilotAction"] as JSONObject,
|
||||
CopilotAction,
|
||||
);
|
||||
}
|
||||
|
||||
public static async getActionTypesBasedOnPriority(): Promise<
|
||||
Array<CopilotActionTypePriority>
|
||||
> {
|
||||
const repositorySecretKey: string | null = GetRepositorySecretKey();
|
||||
|
||||
const url: URL = URL.fromString(
|
||||
GetOneUptimeURL().toString() + "/api",
|
||||
).addRoute(
|
||||
`${new CopilotAction().getCrudApiPath()?.toString()}/copilot-action-types-by-priority/${repositorySecretKey}`,
|
||||
);
|
||||
|
||||
const actionTypesResult: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.get(url);
|
||||
|
||||
if (actionTypesResult instanceof HTTPErrorResponse) {
|
||||
throw actionTypesResult;
|
||||
}
|
||||
|
||||
const actionTypes: Array<CopilotActionTypePriority> =
|
||||
CopilotActionTypePriority.fromJSONArray(
|
||||
actionTypesResult.data["actionTypes"] as JSONArray,
|
||||
CopilotActionTypePriority,
|
||||
) || [];
|
||||
|
||||
logger.debug(
|
||||
`Copilot action types based on priority: ${JSON.stringify(actionTypes, null, 2)}`,
|
||||
);
|
||||
|
||||
return actionTypes;
|
||||
}
|
||||
|
||||
public static async getActionsToWorkOn(data: {
|
||||
serviceCatalogId: ObjectID;
|
||||
serviceRepositoryId: ObjectID;
|
||||
}): Promise<Array<CopilotAction>> {
|
||||
logger.debug("Getting actions to work on");
|
||||
|
||||
if (!data.serviceCatalogId) {
|
||||
throw new BadDataException("Service Catalog ID is required");
|
||||
}
|
||||
|
||||
const repositorySecretKey: string | null = GetRepositorySecretKey();
|
||||
|
||||
if (!repositorySecretKey) {
|
||||
throw new BadDataException("Repository Secret Key is required");
|
||||
}
|
||||
|
||||
// check actions in queue
|
||||
|
||||
const actionsInQueue: Array<CopilotAction> =
|
||||
await CopilotActionUtil.getInQueueActions({
|
||||
serviceCatalogId: data.serviceCatalogId,
|
||||
});
|
||||
|
||||
if (actionsInQueue.length >= MIN_ITEMS_IN_QUEUE_PER_SERVICE_CATALOG) {
|
||||
logger.debug(
|
||||
`Actions in queue: ${JSON.stringify(actionsInQueue, null, 2)}`,
|
||||
);
|
||||
return actionsInQueue;
|
||||
}
|
||||
|
||||
const actionTypePriorities: Array<CopilotActionTypePriority> =
|
||||
await CopilotActionTypeUtil.getEnabledActionTypesBasedOnPriority();
|
||||
|
||||
logger.debug(
|
||||
"Action type priorities: " +
|
||||
actionTypePriorities.map(
|
||||
(actionTypePriority: CopilotActionTypePriority) => {
|
||||
return actionTypePriority.actionType;
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
for (const actionTypePriority of actionTypePriorities) {
|
||||
logger.debug(
|
||||
`Getting actions for action type: ${actionTypePriority.actionType}`,
|
||||
);
|
||||
|
||||
// get items in queue based on priority
|
||||
const itemsInQueue: number =
|
||||
CopilotActionTypeUtil.getItemsInQueueByPriority(
|
||||
actionTypePriority.priority || 1,
|
||||
);
|
||||
|
||||
// get actions based on priority
|
||||
const actions: Array<CopilotAction> = await CopilotActionUtil.getActions({
|
||||
serviceCatalogId: data.serviceCatalogId,
|
||||
serviceRepositoryId: data.serviceRepositoryId,
|
||||
actionType: actionTypePriority.actionType!,
|
||||
itemsInQueue,
|
||||
});
|
||||
|
||||
// add these actions to the queue
|
||||
actionsInQueue.push(...actions);
|
||||
}
|
||||
|
||||
return actionsInQueue;
|
||||
}
|
||||
|
||||
public static async getActions(data: {
|
||||
serviceCatalogId: ObjectID;
|
||||
serviceRepositoryId: ObjectID;
|
||||
actionType: CopilotActionType;
|
||||
itemsInQueue: number;
|
||||
}): Promise<Array<CopilotAction>> {
|
||||
logger.debug(`Getting actions for action type: ${data.actionType}`);
|
||||
|
||||
if (!data.serviceCatalogId) {
|
||||
throw new BadDataException("Service Catalog ID is required");
|
||||
}
|
||||
|
||||
if (!data.actionType) {
|
||||
throw new BadDataException("Action Type is required");
|
||||
}
|
||||
|
||||
const CopilotActionBaseType: typeof CopilotActionBase =
|
||||
ActionDictionary[data.actionType]!;
|
||||
const ActionBase: CopilotActionBase = new CopilotActionBaseType();
|
||||
|
||||
logger.debug(`Getting action props for action type: ${data.actionType}`);
|
||||
|
||||
const actionProps: Array<CopilotActionProp> =
|
||||
await ActionBase.getActionPropsToQueue({
|
||||
serviceCatalogId: data.serviceCatalogId,
|
||||
serviceRepositoryId: data.serviceRepositoryId,
|
||||
maxActionsToQueue: data.itemsInQueue,
|
||||
});
|
||||
|
||||
logger.debug(`Action props for action type: ${data.actionType}`);
|
||||
|
||||
const savedActions: Array<CopilotAction> = [];
|
||||
|
||||
// now these actions need to be saved.
|
||||
for (const actionProp of actionProps) {
|
||||
try {
|
||||
logger.debug(
|
||||
`Creating copilot action for action type: ${data.actionType}`,
|
||||
);
|
||||
|
||||
const savedAction: CopilotAction =
|
||||
await CopilotActionUtil.createCopilotAction({
|
||||
actionType: data.actionType,
|
||||
serviceCatalogId: data.serviceCatalogId,
|
||||
serviceRepositoryId: data.serviceRepositoryId,
|
||||
actionProps: actionProp,
|
||||
});
|
||||
|
||||
logger.debug(
|
||||
`Copilot action created for action type: ${data.actionType}`,
|
||||
);
|
||||
logger.debug(savedAction);
|
||||
|
||||
savedActions.push(savedAction);
|
||||
} catch (error) {
|
||||
logger.error(`Error while adding copilot action: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
return savedActions;
|
||||
}
|
||||
|
||||
public static async updateCopilotAction(data: {
|
||||
actionId: ObjectID;
|
||||
actionStatus: CopilotActionStatus;
|
||||
pullRequestId?: ObjectID | undefined;
|
||||
commitHash?: string | undefined;
|
||||
statusMessage?: string | undefined;
|
||||
logs?: Array<string> | undefined;
|
||||
}): Promise<void> {
|
||||
// send this to the API.
|
||||
const url: URL = URL.fromString(
|
||||
GetOneUptimeURL().toString() + "/api",
|
||||
).addRoute(
|
||||
`${new CopilotAction()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/update-copilot-action/${GetRepositorySecretKey()}`,
|
||||
);
|
||||
|
||||
const codeRepositoryResult: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.post(url, {
|
||||
...data,
|
||||
});
|
||||
|
||||
if (codeRepositoryResult instanceof HTTPErrorResponse) {
|
||||
throw codeRepositoryResult;
|
||||
}
|
||||
}
|
||||
|
||||
public static async createCopilotAction(data: {
|
||||
actionType: CopilotActionType;
|
||||
serviceCatalogId: ObjectID;
|
||||
serviceRepositoryId: ObjectID;
|
||||
actionProps: CopilotActionProp;
|
||||
actionStatus?: CopilotActionStatus;
|
||||
}): Promise<CopilotAction> {
|
||||
const action: CopilotAction = new CopilotAction();
|
||||
action.copilotActionType = data.actionType;
|
||||
action.serviceCatalogId = data.serviceCatalogId;
|
||||
action.serviceRepositoryId = data.serviceRepositoryId;
|
||||
action.copilotActionProp = data.actionProps;
|
||||
action.commitHash = await CodeRepositoryUtil.getCurrentCommitHash();
|
||||
|
||||
if (data.actionStatus) {
|
||||
action.copilotActionStatus = data.actionStatus;
|
||||
} else {
|
||||
action.copilotActionStatus = CopilotActionStatus.IN_QUEUE;
|
||||
}
|
||||
|
||||
// send this to the API.
|
||||
const url: URL = URL.fromString(
|
||||
GetOneUptimeURL().toString() + "/api",
|
||||
).addRoute(
|
||||
`${new CopilotAction()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/create-copilot-action/${GetRepositorySecretKey()}`,
|
||||
);
|
||||
|
||||
const codeRepositoryResult: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.post(url, {
|
||||
copilotAction: CopilotAction.toJSON(action, CopilotAction),
|
||||
});
|
||||
|
||||
if (codeRepositoryResult instanceof HTTPErrorResponse) {
|
||||
throw codeRepositoryResult;
|
||||
}
|
||||
|
||||
const copilotAction: CopilotAction = CopilotAction.fromJSONObject(
|
||||
codeRepositoryResult.data as JSONObject,
|
||||
CopilotAction,
|
||||
);
|
||||
|
||||
if (!copilotAction) {
|
||||
throw new BadDataException("Copilot action not created");
|
||||
}
|
||||
|
||||
if (!copilotAction._id) {
|
||||
throw new BadDataException("Copilot action ID not created");
|
||||
}
|
||||
|
||||
return copilotAction;
|
||||
}
|
||||
|
||||
public static async getInQueueActions(data: {
|
||||
serviceCatalogId: ObjectID;
|
||||
}): Promise<Array<CopilotAction>> {
|
||||
if (!data.serviceCatalogId) {
|
||||
throw new BadDataException("Service Catalog ID is required");
|
||||
}
|
||||
|
||||
const repositorySecretKey: string | null = GetRepositorySecretKey();
|
||||
|
||||
if (!repositorySecretKey) {
|
||||
throw new BadDataException("Repository Secret Key is required");
|
||||
}
|
||||
|
||||
const url: URL = URL.fromString(
|
||||
GetOneUptimeURL().toString() + "/api",
|
||||
).addRoute(
|
||||
`${new CopilotAction()
|
||||
.getCrudApiPath()
|
||||
?.toString()}/copilot-actions-in-queue/${repositorySecretKey}`,
|
||||
);
|
||||
|
||||
const copilotActionsResult: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.get(url, {
|
||||
serviceCatalogId: data.serviceCatalogId.toString(),
|
||||
});
|
||||
|
||||
if (copilotActionsResult instanceof HTTPErrorResponse) {
|
||||
throw copilotActionsResult;
|
||||
}
|
||||
|
||||
const copilotActions: Array<CopilotAction> =
|
||||
CopilotAction.fromJSONArray(
|
||||
copilotActionsResult.data["copilotActions"] as JSONArray,
|
||||
CopilotAction,
|
||||
) || [];
|
||||
|
||||
logger.debug(
|
||||
`Copilot actions in queue for service catalog id: ${data.serviceCatalogId}`,
|
||||
);
|
||||
|
||||
logger.debug(`Copilot events: ${JSON.stringify(copilotActions, null, 2)}`);
|
||||
|
||||
return copilotActions;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import CopilotActionTypePriority from "Common/Models/DatabaseModels/CopilotActionTypePriority";
|
||||
import CopilotActionType, {
|
||||
CopilotActionTypeUtil as ActionTypeUtil,
|
||||
CopilotActionTypeData,
|
||||
} from "Common/Types/Copilot/CopilotActionType";
|
||||
import CopilotActionUtil from "./CopilotAction";
|
||||
import { ActionDictionary } from "../Service/CopilotActions/Index";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
|
||||
export default class CopilotActionTypeUtil {
|
||||
private static isActionEnabled(actionType: CopilotActionType): boolean {
|
||||
return Boolean(ActionDictionary[actionType]); // if action is not in dictionary then it is not enabled
|
||||
}
|
||||
|
||||
public static async getEnabledActionTypesBasedOnPriority(): Promise<
|
||||
Array<CopilotActionTypePriority>
|
||||
> {
|
||||
// if there are no actions then, get actions based on priority
|
||||
const actionTypes: Array<CopilotActionTypePriority> =
|
||||
await CopilotActionUtil.getActionTypesBasedOnPriority();
|
||||
|
||||
const enabledActions: Array<CopilotActionTypePriority> = [];
|
||||
|
||||
for (const actionType of actionTypes) {
|
||||
if (this.isActionEnabled(actionType.actionType!)) {
|
||||
enabledActions.push(actionType);
|
||||
}
|
||||
}
|
||||
|
||||
return enabledActions;
|
||||
}
|
||||
|
||||
public static getItemsInQueueByPriority(priority: number): number {
|
||||
// so if the priority is 1, then there will be 5 items in queue. If the priority is 5, then there will be 1 item in queue.
|
||||
const itemsInQueue: number = 6;
|
||||
return itemsInQueue - priority;
|
||||
}
|
||||
|
||||
public static printEnabledAndDisabledActionTypes(): void {
|
||||
const allActionTypes: Array<CopilotActionTypeData> =
|
||||
ActionTypeUtil.getAllCopilotActionTypes();
|
||||
|
||||
// log all the actions from these actions that are in Action dictionary
|
||||
const enabledActionTypesData: Array<CopilotActionTypeData> =
|
||||
allActionTypes.filter((actionTypeData: CopilotActionTypeData) => {
|
||||
return this.isActionEnabled(actionTypeData.type);
|
||||
});
|
||||
|
||||
const disabledActionTypesData: Array<CopilotActionTypeData> =
|
||||
allActionTypes.filter((actionTypeData: CopilotActionTypeData) => {
|
||||
return !this.isActionEnabled(actionTypeData.type);
|
||||
});
|
||||
|
||||
logger.info("--------------------");
|
||||
logger.info("Copilot will fix the following issues:");
|
||||
for (const actionTypeData of enabledActionTypesData) {
|
||||
logger.info(`- ${actionTypeData.type}`);
|
||||
}
|
||||
|
||||
logger.info("--------------------");
|
||||
logger.info(
|
||||
"Copilot will not fix the following issues at this time (but we will in the future update of the software. We're working on this and they will be launched soon):",
|
||||
);
|
||||
|
||||
for (const disabledTypesData of disabledActionTypesData) {
|
||||
logger.info(`- ${disabledTypesData.type}`);
|
||||
}
|
||||
|
||||
logger.info("--------------------");
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
import TechStack from "Common/Types/ServiceCatalog/TechStack";
|
||||
|
||||
export default class ServiceFileTypesUtil {
|
||||
private static getCommonDirectoriesToIgnore(): string[] {
|
||||
return [
|
||||
"node_modules",
|
||||
".git",
|
||||
"build",
|
||||
"dist",
|
||||
"coverage",
|
||||
"logs",
|
||||
"tmp",
|
||||
"temp",
|
||||
"temporal",
|
||||
"tempfiles",
|
||||
"tempfiles",
|
||||
];
|
||||
}
|
||||
|
||||
private static getCommonFilesToIgnore(): string[] {
|
||||
return [".DS_Store", "Thumbs.db", ".gitignore", ".gitattributes"];
|
||||
}
|
||||
|
||||
public static getCommonFilesToIgnoreByTechStackItem(
|
||||
techStack: TechStack,
|
||||
): string[] {
|
||||
let filesToIgnore: string[] = [];
|
||||
|
||||
switch (techStack) {
|
||||
case TechStack.NodeJS:
|
||||
filesToIgnore = ["package-lock.json"];
|
||||
break;
|
||||
case TechStack.Python:
|
||||
filesToIgnore = ["__pycache__"];
|
||||
break;
|
||||
case TechStack.Ruby:
|
||||
filesToIgnore = ["Gemfile.lock"];
|
||||
break;
|
||||
case TechStack.Go:
|
||||
filesToIgnore = ["go.sum", "go.mod"];
|
||||
break;
|
||||
case TechStack.Java:
|
||||
filesToIgnore = ["pom.xml"];
|
||||
break;
|
||||
case TechStack.PHP:
|
||||
filesToIgnore = ["composer.lock"];
|
||||
break;
|
||||
case TechStack.CSharp:
|
||||
filesToIgnore = ["packages", "bin", "obj"];
|
||||
break;
|
||||
case TechStack.CPlusPlus:
|
||||
filesToIgnore = ["build", "CMakeFiles", "CMakeCache.txt", "Makefile"];
|
||||
break;
|
||||
case TechStack.Rust:
|
||||
filesToIgnore = ["Cargo.lock"];
|
||||
break;
|
||||
case TechStack.Swift:
|
||||
filesToIgnore = ["Podfile.lock"];
|
||||
break;
|
||||
case TechStack.Kotlin:
|
||||
filesToIgnore = [
|
||||
"gradle",
|
||||
"build",
|
||||
"gradlew",
|
||||
"gradlew.bat",
|
||||
"gradle.properties",
|
||||
];
|
||||
break;
|
||||
case TechStack.TypeScript:
|
||||
filesToIgnore = ["node_modules", "package-lock.json"];
|
||||
break;
|
||||
case TechStack.JavaScript:
|
||||
filesToIgnore = ["node_modules", "package-lock.json"];
|
||||
break;
|
||||
case TechStack.Shell:
|
||||
filesToIgnore = [];
|
||||
break;
|
||||
case TechStack.React:
|
||||
filesToIgnore = ["node_modules", "package-lock.json"];
|
||||
break;
|
||||
case TechStack.Other:
|
||||
filesToIgnore = [];
|
||||
break;
|
||||
default:
|
||||
filesToIgnore = [];
|
||||
}
|
||||
|
||||
return filesToIgnore;
|
||||
}
|
||||
|
||||
public static getCommonFilesToIgnoreByTechStack(
|
||||
techStack: Array<TechStack>,
|
||||
): string[] {
|
||||
let filesToIgnore: string[] = [];
|
||||
|
||||
for (const stack of techStack) {
|
||||
filesToIgnore = filesToIgnore.concat(
|
||||
this.getCommonFilesToIgnoreByTechStackItem(stack),
|
||||
);
|
||||
}
|
||||
|
||||
return filesToIgnore
|
||||
.concat(this.getCommonFilesToIgnore())
|
||||
.concat(this.getCommonDirectoriesToIgnore());
|
||||
}
|
||||
|
||||
private static getCommonFilesExtentions(): string[] {
|
||||
// return markdown, dockerfile, etc.
|
||||
return [".md", "dockerfile", ".yml", ".yaml", ".sh", ".gitignore"];
|
||||
}
|
||||
|
||||
public static getFileExtentionsByTechStackItem(
|
||||
techStack: TechStack,
|
||||
): string[] {
|
||||
let fileExtentions: Array<string> = [];
|
||||
|
||||
switch (techStack) {
|
||||
case TechStack.NodeJS:
|
||||
fileExtentions = [".js", ".ts", ".json", ".mjs"];
|
||||
break;
|
||||
case TechStack.Python:
|
||||
fileExtentions = [".py"];
|
||||
break;
|
||||
case TechStack.Ruby:
|
||||
fileExtentions = [".rb"];
|
||||
break;
|
||||
case TechStack.Go:
|
||||
fileExtentions = [".go"];
|
||||
break;
|
||||
case TechStack.Java:
|
||||
fileExtentions = [".java"];
|
||||
break;
|
||||
case TechStack.PHP:
|
||||
fileExtentions = [".php"];
|
||||
break;
|
||||
case TechStack.CSharp:
|
||||
fileExtentions = [".cs"];
|
||||
break;
|
||||
case TechStack.CPlusPlus:
|
||||
fileExtentions = [".cpp", ".c"];
|
||||
break;
|
||||
case TechStack.Rust:
|
||||
fileExtentions = [".rs"];
|
||||
break;
|
||||
case TechStack.Swift:
|
||||
fileExtentions = [".swift"];
|
||||
break;
|
||||
case TechStack.Kotlin:
|
||||
fileExtentions = [".kt", ".kts"];
|
||||
break;
|
||||
case TechStack.TypeScript:
|
||||
fileExtentions = [".ts", ".tsx"];
|
||||
break;
|
||||
case TechStack.JavaScript:
|
||||
fileExtentions = [".js", ".jsx"];
|
||||
break;
|
||||
case TechStack.Shell:
|
||||
fileExtentions = [".sh"];
|
||||
break;
|
||||
case TechStack.React:
|
||||
fileExtentions = [".js", ".ts", ".jsx", ".tsx"];
|
||||
break;
|
||||
case TechStack.Other:
|
||||
fileExtentions = [];
|
||||
break;
|
||||
default:
|
||||
fileExtentions = [];
|
||||
}
|
||||
|
||||
return fileExtentions;
|
||||
}
|
||||
|
||||
public static getFileExtentionsByTechStack(
|
||||
techStack: Array<TechStack>,
|
||||
): string[] {
|
||||
let fileExtentions: Array<string> = [];
|
||||
|
||||
for (let i: number = 0; i < techStack.length; i++) {
|
||||
if (!techStack[i]) {
|
||||
continue;
|
||||
}
|
||||
fileExtentions = fileExtentions.concat(
|
||||
this.getFileExtentionsByTechStackItem(techStack[i]!),
|
||||
);
|
||||
}
|
||||
|
||||
// add common files extentions
|
||||
|
||||
return fileExtentions.concat(this.getCommonFilesExtentions());
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import {
|
||||
GetCodeRepositoryPassword,
|
||||
GetLlmServerUrl,
|
||||
GetLlmType,
|
||||
GetOneUptimeURL,
|
||||
GetRepositorySecretKey,
|
||||
} from "../Config";
|
||||
import CodeRepositoryUtil, { CodeRepositoryResult } from "./CodeRepository";
|
||||
import CodeRepositoryType from "Common/Types/CodeRepository/CodeRepositoryType";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import URL from "Common/Types/API/URL";
|
||||
import LlmType from "../Types/LlmType";
|
||||
import API from "Common/Utils/API";
|
||||
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
|
||||
import HTTPResponse from "Common/Types/API/HTTPResponse";
|
||||
import { JSONObject } from "Common/Types/JSON";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import CopilotActionTypeUtil from "./CopilotActionTypes";
|
||||
|
||||
export default class InitUtil {
|
||||
public static async init(): Promise<CodeRepositoryResult> {
|
||||
if (GetLlmType() === LlmType.ONEUPTIME_LLM) {
|
||||
const llmServerUrl: URL = GetLlmServerUrl();
|
||||
// check status of ll, server
|
||||
const result: HTTPErrorResponse | HTTPResponse<JSONObject> =
|
||||
await API.get(URL.fromString(llmServerUrl.toString()));
|
||||
|
||||
if (result instanceof HTTPErrorResponse) {
|
||||
throw new BadDataException(
|
||||
"OneUptime LLM server is not reachable. Please check the server URL in the environment variables.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// check if oneuptime server is up.
|
||||
const oneuptimeServerUrl: URL = GetOneUptimeURL();
|
||||
const result: HTTPErrorResponse | HTTPResponse<JSONObject> = await API.get(
|
||||
URL.fromString(oneuptimeServerUrl.toString() + "/status"),
|
||||
);
|
||||
|
||||
if (result instanceof HTTPErrorResponse) {
|
||||
throw new BadDataException(
|
||||
`OneUptime ${GetOneUptimeURL().toString()} is not reachable. Please check the server URL in the environment variables.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!GetRepositorySecretKey()) {
|
||||
throw new BadDataException("Repository Secret Key is required");
|
||||
}
|
||||
|
||||
const codeRepositoryResult: CodeRepositoryResult =
|
||||
await CodeRepositoryUtil.getCodeRepositoryResult();
|
||||
|
||||
// Check if the repository type is GitHub and the GitHub token is provided
|
||||
|
||||
if (codeRepositoryResult.serviceRepositories.length === 0) {
|
||||
logger.error(
|
||||
"No services found in the repository. Please add services to the repository in OneUptime Dashboard.",
|
||||
);
|
||||
|
||||
throw new BadDataException(
|
||||
"No services found in the repository. Please add services to the repository in OneUptime Dashboard.",
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
codeRepositoryResult.codeRepository.repositoryHostedAt ===
|
||||
CodeRepositoryType.GitHub &&
|
||||
!GetCodeRepositoryPassword()
|
||||
) {
|
||||
throw new BadDataException(
|
||||
"GitHub token is required for this repository. Please provide the GitHub token in the environment variables.",
|
||||
);
|
||||
}
|
||||
|
||||
// check copilot action types enabled and print it out for user.
|
||||
CopilotActionTypeUtil.printEnabledAndDisabledActionTypes();
|
||||
|
||||
return codeRepositoryResult;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export default class ProcessUtil {
|
||||
public static haltProcessWithSuccess(): void {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import CopilotPullRequest from "Common/Models/DatabaseModels/CopilotPullRequest";
|
||||
import CopilotPullRequestService from "../Service/CopilotPullRequest";
|
||||
import PullRequestState from "Common/Types/CodeRepository/PullRequestState";
|
||||
|
||||
export default class PullRequestUtil {
|
||||
public static async getOpenPRs(): Promise<Array<CopilotPullRequest>> {
|
||||
const openPRs: Array<CopilotPullRequest> = [];
|
||||
|
||||
// get all open pull requests.
|
||||
const openPullRequests: Array<CopilotPullRequest> =
|
||||
await CopilotPullRequestService.getOpenPullRequestsFromDatabase();
|
||||
|
||||
for (const openPullRequest of openPullRequests) {
|
||||
// refresh status of this PR.
|
||||
|
||||
if (!openPullRequest.pullRequestId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pullRequestState: PullRequestState =
|
||||
await CopilotPullRequestService.refreshPullRequestStatus({
|
||||
copilotPullRequest: openPullRequest,
|
||||
});
|
||||
|
||||
if (pullRequestState === PullRequestState.Open) {
|
||||
openPRs.push(openPullRequest);
|
||||
}
|
||||
}
|
||||
|
||||
return openPRs;
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
import ServiceFileTypesUtil from "./FileTypes";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import TechStack from "Common/Types/ServiceCatalog/TechStack";
|
||||
import CodeRepositoryCommonServerUtil from "Common/Server/Utils/CodeRepository/CodeRepository";
|
||||
import CodeRepositoryFile from "Common/Server/Utils/CodeRepository/CodeRepositoryFile";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import ServiceCopilotCodeRepository from "Common/Models/DatabaseModels/ServiceCopilotCodeRepository";
|
||||
import ServiceLanguageUtil from "Common/Utils/TechStack";
|
||||
import CodeRepositoryUtil, {
|
||||
CodeRepositoryResult,
|
||||
ServiceToImproveResult,
|
||||
} from "./CodeRepository";
|
||||
import PullRequestUtil from "./PullRequest";
|
||||
import CopilotPullRequest from "Common/Models/DatabaseModels/CopilotPullRequest";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import ProcessUtil from "./Process";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
|
||||
export default class ServiceRepositoryUtil {
|
||||
public static codeRepositoryResult: CodeRepositoryResult | null = null;
|
||||
public static servicesToImprove: Array<ServiceCopilotCodeRepository> | null =
|
||||
null;
|
||||
|
||||
public static setCodeRepositoryResult(data: {
|
||||
codeRepositoryResult: CodeRepositoryResult;
|
||||
}): void {
|
||||
ServiceRepositoryUtil.codeRepositoryResult = data.codeRepositoryResult;
|
||||
}
|
||||
|
||||
public static async getServicesToImprove(): Promise<
|
||||
Array<ServiceCopilotCodeRepository>
|
||||
> {
|
||||
if (this.servicesToImprove) {
|
||||
return this.servicesToImprove;
|
||||
}
|
||||
|
||||
const codeRepositoryResult: CodeRepositoryResult =
|
||||
ServiceRepositoryUtil.codeRepositoryResult!;
|
||||
|
||||
if (!codeRepositoryResult) {
|
||||
throw new BadDataException("Code repository result is not set");
|
||||
}
|
||||
|
||||
// before cloning the repo, check if there are any services to improve.
|
||||
const openPullRequests: Array<CopilotPullRequest> =
|
||||
await PullRequestUtil.getOpenPRs();
|
||||
|
||||
const servicesToImproveResult: Array<ServiceToImproveResult> =
|
||||
await CodeRepositoryUtil.getServicesToImproveCode({
|
||||
codeRepository: codeRepositoryResult.codeRepository,
|
||||
serviceRepositories: codeRepositoryResult.serviceRepositories,
|
||||
openPullRequests: openPullRequests,
|
||||
});
|
||||
|
||||
const servicesToImprove: Array<ServiceCopilotCodeRepository> =
|
||||
servicesToImproveResult.map(
|
||||
(serviceToImproveResult: ServiceToImproveResult) => {
|
||||
return serviceToImproveResult.serviceRepository;
|
||||
},
|
||||
);
|
||||
|
||||
if (servicesToImprove.length === 0) {
|
||||
logger.info("No services to improve. Exiting.");
|
||||
ProcessUtil.haltProcessWithSuccess();
|
||||
}
|
||||
|
||||
this.servicesToImprove = servicesToImprove;
|
||||
|
||||
return servicesToImprove;
|
||||
}
|
||||
|
||||
public static async getFileLanguage(data: {
|
||||
filePath: string;
|
||||
}): Promise<TechStack> {
|
||||
const fileExtention: string = LocalFile.getFileExtension(data.filePath);
|
||||
|
||||
const techStack: TechStack = ServiceLanguageUtil.getLanguageByFileExtension(
|
||||
{
|
||||
fileExtension: fileExtention,
|
||||
},
|
||||
);
|
||||
|
||||
return techStack;
|
||||
}
|
||||
|
||||
public static async getFileContent(data: {
|
||||
filePath: string;
|
||||
}): Promise<string> {
|
||||
const { filePath } = data;
|
||||
|
||||
const fileContent: string =
|
||||
await CodeRepositoryCommonServerUtil.getFileContent({
|
||||
repoPath: CodeRepositoryUtil.getLocalRepositoryPath(),
|
||||
filePath: filePath,
|
||||
});
|
||||
|
||||
return fileContent;
|
||||
}
|
||||
|
||||
public static async getFilesByServiceCatalogId(data: {
|
||||
serviceCatalogId: ObjectID;
|
||||
}): Promise<Dictionary<CodeRepositoryFile>> {
|
||||
const { serviceCatalogId } = data;
|
||||
|
||||
const serviceRepository: ServiceCopilotCodeRepository | undefined = (
|
||||
await ServiceRepositoryUtil.getServicesToImprove()
|
||||
).find((serviceRepository: ServiceCopilotCodeRepository) => {
|
||||
return (
|
||||
serviceRepository.serviceCatalog!.id?.toString() ===
|
||||
serviceCatalogId.toString()
|
||||
);
|
||||
});
|
||||
|
||||
if (!serviceRepository) {
|
||||
throw new BadDataException("Service repository not found");
|
||||
}
|
||||
|
||||
const allFiles: Dictionary<CodeRepositoryFile> =
|
||||
await ServiceRepositoryUtil.getFilesInServiceDirectory({
|
||||
serviceRepository,
|
||||
});
|
||||
|
||||
return allFiles;
|
||||
}
|
||||
|
||||
public static async getFilesInServiceDirectory(data: {
|
||||
serviceRepository: ServiceCopilotCodeRepository;
|
||||
}): Promise<Dictionary<CodeRepositoryFile>> {
|
||||
const { serviceRepository } = data;
|
||||
|
||||
if (!serviceRepository.serviceCatalog?.techStack) {
|
||||
throw new BadDataException(
|
||||
"Service language is not defined in the service catalog",
|
||||
);
|
||||
}
|
||||
|
||||
const allFiles: Dictionary<CodeRepositoryFile> =
|
||||
await CodeRepositoryCommonServerUtil.getFilesInDirectoryRecursive({
|
||||
repoPath: CodeRepositoryUtil.getLocalRepositoryPath(),
|
||||
directoryPath: serviceRepository.servicePathInRepository || ".",
|
||||
acceptedFileExtensions:
|
||||
ServiceFileTypesUtil.getFileExtentionsByTechStack(
|
||||
serviceRepository.serviceCatalog!.techStack!,
|
||||
),
|
||||
ignoreFilesOrDirectories:
|
||||
ServiceFileTypesUtil.getCommonFilesToIgnoreByTechStack(
|
||||
serviceRepository.serviceCatalog!.techStack!,
|
||||
),
|
||||
});
|
||||
|
||||
return allFiles;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
|
||||
"preset": "ts-jest",
|
||||
"testPathIgnorePatterns": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
],
|
||||
"verbose": true,
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"tsconfig": "tsconfig.json",
|
||||
"babelConfig": false
|
||||
}
|
||||
},
|
||||
"moduleFileExtensions": ["ts", "js", "json"],
|
||||
"transform": {
|
||||
".(ts|tsx)": "ts-jest"
|
||||
},
|
||||
"testEnvironment": "node",
|
||||
"collectCoverage": false,
|
||||
"coverageReporters": ["text", "lcov"],
|
||||
"testRegex": "./Tests/(.*).test.ts",
|
||||
"collectCoverageFrom": ["./**/*.(tsx||ts)"],
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"lines": 0,
|
||||
"functions": 0,
|
||||
"branches": 0,
|
||||
"statements": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"watch": [
|
||||
"./",
|
||||
"../Common"
|
||||
],
|
||||
"ext": "ts,json,tsx,env,js,jsx,hbs",
|
||||
"exec": "node --inspect=0.0.0.0:9229 --require ts-node/register Index.ts"
|
||||
}
|
||||
9145
Copilot/package-lock.json
generated
9145
Copilot/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"name": "@oneuptime/copilot",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "export NODE_OPTIONS='--max-old-space-size=8096' && node --require ts-node/register Index.ts",
|
||||
"compile": "tsc",
|
||||
"clear-modules": "rm -rf node_modules && rm package-lock.json && npm install",
|
||||
"dev": "npx nodemon",
|
||||
"audit": "npm audit --audit-level=low",
|
||||
"dep-check": "npm install -g depcheck && depcheck ./ --skip-missing=true",
|
||||
"test": "jest --passWithNoTests"
|
||||
},
|
||||
"author": "OneUptime <hello@oneuptime.com> (https://oneuptime.com/)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"Common": "file:../Common",
|
||||
|
||||
"dotenv": "^16.4.5",
|
||||
"openai": "^4.52.5",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.5.0",
|
||||
"@types/node": "^17.0.31",
|
||||
"jest": "^28.1.0",
|
||||
"nodemon": "^2.0.20",
|
||||
"ts-jest": "^28.0.2"
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
{
|
||||
"ts-node": {
|
||||
// these options are overrides used only by ts-node
|
||||
// same as the --compilerOptions flag and the TS_NODE_COMPILER_OPTIONS environment variable
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"resolveJsonModule": true,
|
||||
}
|
||||
},
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2017" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
"jsx": "react" /* Specify what JSX code is generated. */,
|
||||
"experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
|
||||
"emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */,
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
|
||||
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
|
||||
/* Modules */
|
||||
// "module": "es2022" /* Specify what module code is generated. */,
|
||||
"rootDir": "" /* Specify the root folder within your source files. */,
|
||||
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
] /* Specify multiple folders that act like `./node_modules/@types`. */,
|
||||
"types": [
|
||||
"node",
|
||||
"jest"
|
||||
] /* Specify type package names to be included without being referenced in a source file. */,
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files */
|
||||
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
"sourceMap": true /* Create source map files for emitted JavaScript files. */,
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "build/dist" /* Specify an output folder for all emitted files. */,
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied `any` type.. */,
|
||||
"strictNullChecks": true /* When type checking, take into account `null` and `undefined`. */,
|
||||
"strictFunctionTypes": true /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */,
|
||||
"strictBindCallApply": true /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */,
|
||||
"strictPropertyInitialization": true /* Check for class properties that are declared but not set in the constructor. */,
|
||||
"noImplicitThis": true /* Enable error reporting when `this` is given the type `any`. */,
|
||||
"useUnknownInCatchVariables": true /* Type catch clause variables as 'unknown' instead of 'any'. */,
|
||||
"alwaysStrict": true /* Ensure 'use strict' is always emitted. */,
|
||||
"noUnusedLocals": true /* Enable error reporting when a local variables aren't read. */,
|
||||
"noUnusedParameters": true /* Raise an error when a function parameter isn't read */,
|
||||
"exactOptionalPropertyTypes": true /* Interpret optional property types as written, rather than adding 'undefined'. */,
|
||||
"noImplicitReturns": true /* Enable error reporting for codepaths that do not explicitly return in a function. */,
|
||||
"noFallthroughCasesInSwitch": true /* Enable error reporting for fallthrough cases in switch statements. */,
|
||||
"noUncheckedIndexedAccess": true /* Include 'undefined' in index signature results */,
|
||||
"noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */,
|
||||
"noPropertyAccessFromIndexSignature": true /* Enforces using indexed accessors for keys declared using an indexed type */,
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user