diff --git a/package.json b/package.json index 620c8d1..bc978b8 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "typescript": "^4.1.2" }, "dependencies": { + "@gitbeaker/node": "^34.1.0", "@octokit/core": "^3.2.4", "@octokit/rest": "^18.0.12", "js-yaml": "^4.1.0", diff --git a/sample/config/config.yaml b/sample/config/config-github.yaml similarity index 100% rename from sample/config/config.yaml rename to sample/config/config-github.yaml diff --git a/sample/config/config-gitlab.yaml b/sample/config/config-gitlab.yaml new file mode 100644 index 0000000..758ee08 --- /dev/null +++ b/sample/config/config-gitlab.yaml @@ -0,0 +1,10 @@ +repo: + url: https://gitlab.com/codeleague/codecoach + pr: 40 + token: 123qwe123qwe123qwe123 + gitlabProjectId: "12" +buildLogFiles: + - type: tslint + path: /path/to/log.json + cwd: /repo/src +output: ./output.json diff --git a/src/Config/@types/configArgument.ts b/src/Config/@types/configArgument.ts index ab7ffb3..89f4550 100644 --- a/src/Config/@types/configArgument.ts +++ b/src/Config/@types/configArgument.ts @@ -1,6 +1,6 @@ import { BuildLogFile } from './buildLogFile'; -export type ConfigArgument = { +export interface ConfigArgument { url: string; pr: number; buildLogFile: BuildLogFile[]; @@ -8,4 +8,8 @@ export type ConfigArgument = { token: string; removeOldComment: boolean; config: string; -}; +} + +export interface ConfigArgumentGitlab extends ConfigArgument { + gitlabProjectId: string | number; +} diff --git a/src/Config/@types/configYAML.ts b/src/Config/@types/configYAML.ts index ca5d438..cf97c5f 100644 --- a/src/Config/@types/configYAML.ts +++ b/src/Config/@types/configYAML.ts @@ -1,14 +1,20 @@ import { BuildLogFile } from './buildLogFile'; +export interface RepoDetailsYAML { + url: string; + pr: number; + token: string; + userAgent: string; + timeZone: string; + removeOldComment: boolean; +} + +export interface RepoDetailsGitlabYAML extends RepoDetailsYAML { + gitlabProjectId: string | number; +} + export type ConfigYAML = { - repo: { - url: string; - pr: number; - token: string; - userAgent: string; - timeZone: string; - removeOldComment: boolean; - }; + repo: RepoDetailsGitlabYAML; buildLogFiles: BuildLogFile[]; output: string; }; diff --git a/src/Config/@types/providerConfig.ts b/src/Config/@types/providerConfig.ts index bb3f034..9214c99 100644 --- a/src/Config/@types/providerConfig.ts +++ b/src/Config/@types/providerConfig.ts @@ -3,4 +3,5 @@ export type ProviderConfig = { repoUrl: string; prId: number; removeOldComment: boolean; + gitlabProjectId: string | number; }; diff --git a/src/Config/Config.spec.ts b/src/Config/Config.spec.ts index 548902d..734ef5d 100644 --- a/src/Config/Config.spec.ts +++ b/src/Config/Config.spec.ts @@ -11,10 +11,28 @@ const MOCK_ARGS = [ '-o=./tmp/out.json', ]; -const MOCK_ARGS_W_CONFIG_YAML = [ +const MOCK_ARGS_FOR_GITLAB = [ '/usr/local/Cellar/node/15.13.0/bin/node', '/Users/codecoach/src/app.ts', - '--config=sample/config/config.yaml', + '--url=https://gitlab.com/codeleague/codecoach.git', + '--removeOldComment', + '--token=placeyourtokenhere', + '--pr=15', + '--gitlabProjectId=12', + '-f=dotnetbuild;./sample/dotnetbuild/build.content;/repo/src', + '-o=./tmp/out.json', +]; + +const MOCK_ARGS_W_CONFIG_YAML_FOR_GITHUB = [ + '/usr/local/Cellar/node/15.13.0/bin/node', + '/Users/codecoach/src/app.ts', + '--config=sample/config/config-github.yaml', +]; + +const MOCK_ARGS_W_CONFIG_YAML_FOR_GITLAB = [ + '/usr/local/Cellar/node/15.13.0/bin/node', + '/Users/codecoach/src/app.ts', + '--config=sample/config/config-gitlab.yaml', ]; export const EXPECTED_MOCK_ARGS = [ @@ -28,6 +46,17 @@ export const EXPECTED_MOCK_ARGS = [ './tmp/out.json', ]; +export const EXPECTED_MOCK_ARGS_FOR_GITLAB = [ + '/usr/local/Cellar/node/15.13.0/bin/node', + '/Users/codecoach/src/app.ts', + 'https://gitlab.com/codeleague/codecoach.git', + true, + 'placeyourtokenhere', + 15, + '12', + 'dotnetbuild;./sample/dotnetbuild/build.content;/repo/src', + './tmp/out.json', +]; describe('Config Test', () => { let config: typeof Config; @@ -44,9 +73,31 @@ describe('Config Test', () => { }); it('Should able to use a config file without passing other args', async () => { - process.argv = MOCK_ARGS_W_CONFIG_YAML; + process.argv = MOCK_ARGS_W_CONFIG_YAML_FOR_GITHUB; + config = (await import('./Config')).Config; + const fullfillConfig = await config; + expect(fullfillConfig.app.buildLogFiles[0].type).toBe('tslint'); + }); + + // gitlab args should pass when project id is present + it('Should able to parse this args and run without throwing error', async () => { + process.argv = MOCK_ARGS_FOR_GITLAB; + config = (await import('./Config')).Config; + const fullfillConfig = await config; + expect(fullfillConfig.provider.repoUrl).toBe(EXPECTED_MOCK_ARGS_FOR_GITLAB[2]); + expect(fullfillConfig.provider.removeOldComment).toBe( + EXPECTED_MOCK_ARGS_FOR_GITLAB[3], + ); + expect(fullfillConfig.provider.gitlabProjectId).toBe( + EXPECTED_MOCK_ARGS_FOR_GITLAB[6], + ); + }); + + it('Should able to use a config file without passing other args', async () => { + process.argv = MOCK_ARGS_W_CONFIG_YAML_FOR_GITLAB; config = (await import('./Config')).Config; const fullfillConfig = await config; expect(fullfillConfig.app.buildLogFiles[0].type).toBe('tslint'); + expect(fullfillConfig.provider.gitlabProjectId).toBe('12'); }); }); diff --git a/src/Config/Config.ts b/src/Config/Config.ts index d66d1b8..a19d576 100644 --- a/src/Config/Config.ts +++ b/src/Config/Config.ts @@ -3,7 +3,10 @@ import { ProjectType } from './@enums'; import { BuildLogFile, ConfigArgument, ConfigObject } from './@types'; import { buildAppConfig, buildProviderConfig } from './configBuilder'; import { DEFAULT_OUTPUT_FILE } from './constants/defaults'; -import { REQUIRED_ARGS } from './constants/required'; +import { isGitlab } from '../Provider/utils/vcsType'; + +import { REQUIRED_ARGS_GITHUB, REQUIRED_ARGS_GITLAB } from './constants/required'; +import { ConfigArgumentGitlab } from './@types/configArgument'; const projectTypes = Object.keys(ProjectType); @@ -24,6 +27,10 @@ const args = yargs describe: 'GitHub token', type: 'string', }) + .option('gitlabProjectId', { + describe: 'Gitlab Project Id', + type: 'string', + }) .option('buildLogFile', { alias: 'f', describe: `Build log content files formatted in ';[;]' @@ -56,11 +63,14 @@ and is build root directory (optional (Will use current context as cwd)). .check((options) => { // check required arguments const useConfigArgs = options.config === undefined; - const validRequiredArgs = REQUIRED_ARGS.every( + const requiredArgs = isGitlab(options.url) + ? REQUIRED_ARGS_GITLAB + : REQUIRED_ARGS_GITHUB; + const validRequiredArgs = requiredArgs.every( (el) => options[el] != undefined || options[el] != null, ); if (useConfigArgs && !validRequiredArgs) - throw `please fill all required fields ${REQUIRED_ARGS.join(', ')}`; + throw `please fill all required fields ${requiredArgs.join(', ')}`; return true; }) .check((options) => { @@ -76,7 +86,7 @@ and is build root directory (optional (Will use current context as cwd)). }) .help() .wrap(120) - .parse(process.argv.slice(1)) as ConfigArgument; + .parse(process.argv.slice(1)) as ConfigArgumentGitlab; export const Config: Promise = (async () => { return Object.freeze({ diff --git a/src/Config/YML.spec.ts b/src/Config/YML.spec.ts index 7a55a06..260cf40 100644 --- a/src/Config/YML.spec.ts +++ b/src/Config/YML.spec.ts @@ -2,9 +2,17 @@ import { YML } from './YML'; describe('YML Test', () => { it('Should able to validate yaml file correctly', async () => { - const config = await YML.parse('sample/config/config.yaml'); + const config = await YML.parse('sample/config/config-github.yaml'); expect(config.output).toBe('./output.json'); expect(config.repo.pr).toBe(40); expect(config.repo.removeOldComment).toBe(false); }); + + it('Should able to validate yaml file correctly', async () => { + const config = await YML.parse('sample/config/config-gitlab.yaml'); + expect(config.output).toBe('./output.json'); + expect(config.repo.pr).toBe(40); + expect(config.repo.gitlabProjectId).toBe('12'); + expect(config.repo.removeOldComment).toBe(false); + }); }); diff --git a/src/Config/YML.ts b/src/Config/YML.ts index 1345d8e..bc90cbe 100644 --- a/src/Config/YML.ts +++ b/src/Config/YML.ts @@ -1,7 +1,12 @@ import { File } from '../File'; import yaml from 'js-yaml'; -import { REQUIRED_YAML_ARGS, REQUIRED_YAML_PROVIDER_ARGS } from './constants/required'; +import { + REQUIRED_YAML_ARGS, + REQUIRED_GITHUB_YAML_PROVIDER_ARGS, + REQUIRED_GITLAB_YAML_PROVIDER_ARGS, +} from './constants/required'; import { ConfigYAML } from './@types/configYAML'; +import { isGitlab } from '../Provider/utils/vcsType'; export class YML { private static transform(config: ConfigYAML): ConfigYAML { @@ -14,11 +19,14 @@ export class YML { ); if (!validRequiredArgs) throw `please fill all required fields ${REQUIRED_YAML_ARGS.join(', ')}`; - const validRequiredProviderArgs = REQUIRED_YAML_PROVIDER_ARGS.every( + const requiredYamlProviderArgs = isGitlab(config.repo.url) + ? REQUIRED_GITLAB_YAML_PROVIDER_ARGS + : REQUIRED_GITHUB_YAML_PROVIDER_ARGS; + const validRequiredProviderArgs = requiredYamlProviderArgs.every( (el) => config.repo[el] != undefined || config.repo[el] != null, ); if (!validRequiredProviderArgs) - throw `please fill all required fields ${REQUIRED_YAML_PROVIDER_ARGS.join(', ')}`; + throw `please fill all required fields ${requiredYamlProviderArgs.join(', ')}`; return { ...config, diff --git a/src/Config/configBuilder.ts b/src/Config/configBuilder.ts index f686039..636444d 100644 --- a/src/Config/configBuilder.ts +++ b/src/Config/configBuilder.ts @@ -1,4 +1,5 @@ import { AppConfig, ConfigArgument, ProviderConfig } from './@types'; +import { ConfigArgumentGitlab } from './@types/configArgument'; import { YML } from './YML'; const buildYMLConfig = async (args: ConfigArgument) => { @@ -7,7 +8,7 @@ const buildYMLConfig = async (args: ConfigArgument) => { }; export const buildProviderConfig = async ( - arg: ConfigArgument, + arg: ConfigArgumentGitlab, ): Promise => { const configFile = await buildYMLConfig(arg); @@ -16,6 +17,7 @@ export const buildProviderConfig = async ( repoUrl: configFile?.repo.url || arg.url, prId: configFile?.repo.pr || arg.pr, removeOldComment: configFile?.repo.removeOldComment || arg.removeOldComment, + gitlabProjectId: configFile?.repo.gitlabProjectId || arg.gitlabProjectId, }; }; diff --git a/src/Config/constants/required.ts b/src/Config/constants/required.ts index 691a375..cb307ee 100644 --- a/src/Config/constants/required.ts +++ b/src/Config/constants/required.ts @@ -1,14 +1,26 @@ -import { ConfigArgument } from '..'; +import { ConfigArgumentGitlab } from '../@types/configArgument'; import { ConfigYAML } from '../@types/configYAML'; -type RequiredArgs = (keyof ConfigArgument)[]; -export const REQUIRED_ARGS: RequiredArgs = ['url', 'pr', 'buildLogFile', 'token']; +export type RequiredArgs = (keyof ConfigArgumentGitlab)[]; +export const REQUIRED_ARGS_GITHUB: RequiredArgs = ['url', 'pr', 'buildLogFile', 'token']; +export const REQUIRED_ARGS_GITLAB: RequiredArgs = [ + 'url', + 'pr', + 'buildLogFile', + 'token', + 'gitlabProjectId', +]; type RequiredYamlArgs = (keyof ConfigYAML)[]; export const REQUIRED_YAML_ARGS: RequiredYamlArgs = ['repo', 'buildLogFiles']; -type RequiredYamlProviderArgs = (keyof ConfigYAML['repo'])[]; -export const REQUIRED_YAML_PROVIDER_ARGS: RequiredYamlProviderArgs = [ +export type RequiredYamlProviderArgs = (keyof ConfigYAML['repo'])[]; +export const REQUIRED_GITHUB_YAML_PROVIDER_ARGS: RequiredYamlProviderArgs = [ + 'url', + 'pr', + 'token', +]; +export const REQUIRED_GITLAB_YAML_PROVIDER_ARGS: RequiredYamlProviderArgs = [ 'url', 'pr', 'token', diff --git a/src/Provider/Gitlab/Gitlab.spec.ts b/src/Provider/Gitlab/Gitlab.spec.ts new file mode 100644 index 0000000..3566eb1 --- /dev/null +++ b/src/Provider/Gitlab/Gitlab.spec.ts @@ -0,0 +1,134 @@ +import { LogSeverity } from '../../Parser'; +import { Gitlab } from './Gitlab'; +import { IGitlabMRService } from './IGitlabMRService'; +import { MergeRequestNoteSchema } from '@gitbeaker/core/dist/types/types'; + +const mockedCommentId = 45346; +const mockedUserId = 1234; +const mockedSha = '123456'; +const mockTouchFile = 'file1.cs'; +const file1TouchLine = 11; +const file2TouchLine = 33; +const mockTouchDiff = { + file: mockTouchFile, + patch: [ + { from: file1TouchLine - 1, to: file1TouchLine + 1 }, + { from: file2TouchLine - 2, to: file2TouchLine + 2 }, + ], +}; + +const mockedComments = [ + { id: mockedCommentId, author: { id: mockedUserId } }, +] as MergeRequestNoteSchema[]; + +class MrServiceMock implements IGitlabMRService { + listAllNotes = jest.fn().mockResolvedValue(mockedComments); + createNote = jest.fn().mockResolvedValue(undefined); + createReviewComment = jest.fn().mockResolvedValue(undefined); + deleteNote = jest.fn().mockResolvedValue(undefined); + getCurrentUserId = jest.fn().mockResolvedValue(mockedUserId); + getLatestCommitSha = jest.fn().mockResolvedValue(mockedSha); + diff = jest.fn().mockResolvedValue([mockTouchDiff]); +} + +const touchFileError = { + log: '', + msg: 'msg1', + severity: LogSeverity.error, + source: mockTouchFile, + line: file1TouchLine, + lineOffset: 22, + valid: true, +}; +const touchFileWarning = { + log: '', + msg: 'msg3', + severity: LogSeverity.warning, + source: mockTouchFile, + line: file2TouchLine, + lineOffset: 44, + valid: true, +}; +const untouchedError = { + log: '', + msg: 'msg2', + severity: LogSeverity.error, + source: 'otherfile.cs', + line: 55, + lineOffset: 66, + valid: true, +}; +const untouchedWarning = { + log: '', + msg: 'msg4', + severity: LogSeverity.warning, + source: 'otherfile.cs', + line: 77, + lineOffset: 88, + valid: true, +}; + +describe('VCS: GitHub', () => { + it('should remove old comments and reviews and post new ones', async () => { + const service = new MrServiceMock(); + const github = new Gitlab(service, true); + + await github.report([ + touchFileError, + touchFileWarning, + untouchedError, + untouchedWarning, + ]); + + expect(service.getCurrentUserId).toHaveBeenCalledTimes(1); + expect(service.listAllNotes).toHaveBeenCalledTimes(1); + + expect(service.deleteNote).toHaveBeenCalledTimes(1); + expect(service.deleteNote).toHaveBeenCalledWith(mockedCommentId); + + expect(service.getLatestCommitSha).toHaveBeenCalledTimes(1); + expect(service.diff).toHaveBeenCalledTimes(1); + + expect(service.createReviewComment).toHaveBeenNthCalledWith( + 1, + mockedSha, + expect.any(String), + touchFileError.source, + touchFileError.line, + ); + expect(service.createReviewComment).toHaveBeenNthCalledWith( + 2, + mockedSha, + expect.any(String), + touchFileWarning.source, + touchFileWarning.line, + ); + + expect(service.createNote).toHaveBeenCalledTimes(1); + expect(service.createNote).toHaveBeenCalledWith(expect.any(String)); + }); + + it('should group comments in the same line of same file', async () => { + const service = new MrServiceMock(); + const github = new Gitlab(service); + + await github.report([touchFileError, touchFileError, touchFileError, touchFileError]); + + expect(service.createReviewComment).toHaveBeenCalledTimes(1); + expect(service.createReviewComment).toHaveBeenCalledWith( + mockedSha, + expect.any(String), + touchFileError.source, + touchFileError.line, + ); + }); + + it('should set commit status as success when no error', async () => { + const service = new MrServiceMock(); + const github = new Gitlab(service); + await github.report([touchFileWarning]); + + expect(service.createReviewComment).toHaveBeenCalledTimes(1); + expect(service.createNote).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/Provider/Gitlab/Gitlab.ts b/src/Provider/Gitlab/Gitlab.ts new file mode 100644 index 0000000..fb0fd19 --- /dev/null +++ b/src/Provider/Gitlab/Gitlab.ts @@ -0,0 +1,123 @@ +import { VCS } from '..'; +import { Log } from '../../Logger'; +import { LogSeverity, LogType } from '../../Parser'; +import { Diff } from '../@types/PatchTypes'; +import { onlyIn, onlySeverity } from '../utils/filter.util'; +import { MessageUtil } from '../utils/message.util'; +import { CommentFileStructure, CommentStructure, Comment } from '../@types/CommentTypes'; +import { IGitlabMRService } from './IGitlabMRService'; + +export class Gitlab implements VCS { + private commitId: string; + private touchedDiff: Diff[]; + private comments: Comment[]; + private nWarning: number; + private nError: number; + + constructor( + private readonly mrService: IGitlabMRService, + private readonly removeOldComment: boolean = false, + ) {} + + async report(logs: LogType[]): Promise { + try { + await this.setup(logs); + + if (this.removeOldComment) { + await this.removeExistingComments(); // check if does it remove review comments added on a commit or not + } + + await Promise.all(this.comments.map((c) => this.createReviewComment(c))); + await this.createSummaryComment(); + // Cannot set commit status + + Log.info('Report commit status completed'); + } catch (err) { + Log.error('GitHub report failed', err); + throw err; + } + } + + private async createSummaryComment() { + if (this.nWarning + this.nError > 0) { + const overview = MessageUtil.generateOverviewMessage(this.nError, this.nWarning); + await this.mrService.createNote(overview); + Log.info('Create summary comment completed'); + } else { + Log.info('No summary comment needed'); + } + } + + private async setup(logs: LogType[]) { + this.commitId = await this.mrService.getLatestCommitSha(); + this.touchedDiff = await this.mrService.diff(); + + const touchedFileLog = logs + .filter(onlySeverity(LogSeverity.error, LogSeverity.warning)) + .filter(onlyIn(this.touchedDiff)); + + this.comments = Gitlab.groupComments(touchedFileLog); + this.nError = this.comments.reduce((sum, comment) => sum + comment.errors, 0); + this.nWarning = this.comments.reduce((sum, comment) => sum + comment.warnings, 0); + + Log.debug(`VCS Setup`, { + sha: this.commitId, + diff: this.touchedDiff, + comments: this.comments, + err: this.nError, + warning: this.nWarning, + }); + } + + private async createReviewComment(comment: Comment): Promise { + const { text, file, line } = comment; + + await this.mrService.createReviewComment(this.commitId, text, file, line); + Log.debug('GitHub create review success', { text, file, line }); + return comment; + } + + private async removeExistingComments(): Promise { + const [userId, notes] = await Promise.all([ + this.mrService.getCurrentUserId(), + this.mrService.listAllNotes(), + ]); + Log.debug('Get existing CodeCoach comments completed'); + + const deleteNotes = notes + .filter((note) => note.author.id === userId) + .map((note) => this.mrService.deleteNote(note.id)); + + await Promise.all([...deleteNotes]); + Log.debug('Delete CodeCoach comments completed'); + } + + private static groupComments(logs: LogType[]): Comment[] { + const commentMap = logs.reduce((map: CommentStructure, log) => { + const { source: file, line, severity, msg } = log; + const text = MessageUtil.createMessageWithEmoji(msg, severity); + + if (!line) return map; + + const currentWarnings = map?.[file]?.[line]?.warnings ?? 0; + const currentErrors = map?.[file]?.[line]?.errors ?? 0; + const currentText = map?.[file]?.[line]?.text ?? ''; + + const nextObject: Comment = { + text: `${currentText}\n${text}`, + errors: currentErrors + (severity === LogSeverity.error ? 1 : 0), + warnings: currentWarnings + (severity === LogSeverity.warning ? 1 : 0), + file, + line, + }; + + const fileCommentUpdater: CommentFileStructure = { [line]: nextObject }; + const updatedFileComment = Object.assign({}, map?.[file], fileCommentUpdater); + + const mapUpdater: CommentStructure = { [file]: updatedFileComment }; + return Object.assign({}, map, mapUpdater); + }, {}); + + return Object.values(commentMap).flatMap((file) => Object.values(file)); + } +} diff --git a/src/Provider/Gitlab/GitlabMRService.ts b/src/Provider/Gitlab/GitlabMRService.ts new file mode 100644 index 0000000..a8fc5d7 --- /dev/null +++ b/src/Provider/Gitlab/GitlabMRService.ts @@ -0,0 +1,104 @@ +import { Diff } from '../@types/PatchTypes'; +import { getPatch } from '../utils/patchProcessor'; +import { IGitlabMRService } from './IGitlabMRService'; +import { CommitSchema, MergeRequestNoteSchema } from '@gitbeaker/core/dist/types/types'; +import { Commits, MergeRequestNotes, MergeRequests, Users } from '@gitbeaker/node'; +import { URL } from 'url'; + +export class GitlabMRService implements IGitlabMRService { + private readonly projectId: string | number; + private readonly token: string; + private readonly host: string; + + constructor( + token: string, + repoUrl: string, + private readonly pr: number, + projectId: string | number, + ) { + const repoUrlObj = new URL(repoUrl); + this.host = repoUrlObj.origin; + this.token = token; + this.projectId = projectId; + } + + async getCurrentUserId(): Promise { + const users = new Users({ + host: this.host, + token: this.token, + }); + const user = await users.current(); + return user.id; + } + + async listAllNotes(): Promise { + const mergeRequestNotes = new MergeRequestNotes({ + host: this.host, + token: this.token, + }); + + return await mergeRequestNotes.all(this.projectId, this.pr); + } + + async getLatestCommitSha(): Promise { + const mergeRequests = new MergeRequests({ + host: this.host, + token: this.token, + }); + + const commits = await mergeRequests.commits(this.projectId, this.pr); + const sortedCommits = commits.sort((a: CommitSchema, b: CommitSchema) => { + return a.created_at.getTime() - b.created_at.getTime(); + }); + + return sortedCommits[0].id; + } + + async deleteNote(noteId: number): Promise { + const mergeRequestNotes = new MergeRequestNotes({ + host: this.host, + token: this.token, + }); + + mergeRequestNotes.remove(this.projectId, this.pr, noteId); + } + + // github can do someone fancy shit here we cant + async createNote(note: string): Promise { + const mergeRequestNotes = new MergeRequestNotes({ + host: this.host, + token: this.token, + }); + + mergeRequestNotes.create(this.projectId, this.pr, note); + } + + async createReviewComment( + commitId: string, + note: string, + file: string, + line: number, + ): Promise { + const commit = new Commits({ + host: this.host, + token: this.token, + }); + + commit.createComment(this.projectId, commitId, note, { path: file, line: line }); + } + + async diff(): Promise { + const mergeRequests = new MergeRequests({ + host: this.host, + token: this.token, + }); + + const changes = (await mergeRequests.changes(this.projectId, this.pr)).changes; + + if (!changes) { + return []; + } else { + return changes?.map((d) => ({ file: d.new_path, patch: getPatch(d.diff) })); + } + } +} diff --git a/src/Provider/Gitlab/IGitlabMRService.ts b/src/Provider/Gitlab/IGitlabMRService.ts new file mode 100644 index 0000000..6f93595 --- /dev/null +++ b/src/Provider/Gitlab/IGitlabMRService.ts @@ -0,0 +1,18 @@ +import { CommitStatus } from '../GitHub/CommitStatus'; +import { Diff } from '../@types/PatchTypes'; +import { MergeRequestNoteSchema } from '@gitbeaker/core/dist/types/types'; + +export interface IGitlabMRService { + createNote(note: string): Promise; + createReviewComment( + commit: string, + note: string, + file: string, + line: number, + ): Promise; + listAllNotes(): Promise; + deleteNote(noteId: number): Promise; + getCurrentUserId(): Promise; + getLatestCommitSha(): Promise; + diff(): Promise; +} diff --git a/src/Provider/utils/vcsType.ts b/src/Provider/utils/vcsType.ts new file mode 100644 index 0000000..c69b83c --- /dev/null +++ b/src/Provider/utils/vcsType.ts @@ -0,0 +1,9 @@ +import { URL } from 'url'; + +export function isGitlab(url: string | undefined): boolean { + if (url == null || url == '') { + return false; + } + const urlObj = new URL(url); + return urlObj.hostname.includes('gitlab'); +} diff --git a/src/app.ts b/src/app.ts index 72ae00e..ae6d5da 100644 --- a/src/app.ts +++ b/src/app.ts @@ -15,6 +15,9 @@ import { } from './Parser'; import { DartLintParser } from './Parser/DartLintParser'; import { GitHub, GitHubPRService, VCS } from './Provider'; +import { Gitlab } from './Provider/Gitlab/Gitlab'; +import { GitlabMRService } from './Provider/Gitlab/GitlabMRService'; +import { isGitlab } from './Provider/utils/vcsType'; class App { private vcs: VCS; @@ -22,13 +25,7 @@ class App { async start(): Promise { this.config = await Config; - - const githubPRService = new GitHubPRService( - this.config.provider.token, - this.config.provider.repoUrl, - this.config.provider.prId, - ); - this.vcs = new GitHub(githubPRService, this.config.provider.removeOldComment); + this.vcs = this.getVCS(); const logs = await this.parseBuildData(this.config.app.buildLogFiles); Log.info('Build data parsing completed'); @@ -76,6 +73,31 @@ class App { const config = await Config; await File.writeFileHelper(config.app.logFilePath, JSON.stringify(logs, null, 2)); } + + private getVCS(): VCS { + return isGitlab(this.config.provider.repoUrl) + ? this.buildGitlab() + : this.buildGithub(); + } + + private buildGithub(): VCS { + const githubPRService = new GitHubPRService( + this.config.provider.token, + this.config.provider.repoUrl, + this.config.provider.prId, + ); + return new GitHub(githubPRService, this.config.provider.removeOldComment); + } + + private buildGitlab(): VCS { + const gitlabMRService = new GitlabMRService( + this.config.provider.token, + this.config.provider.repoUrl, + this.config.provider.prId, + this.config.provider.gitlabProjectId, + ); + return new Gitlab(gitlabMRService, this.config.provider.removeOldComment); + } } new App().start().catch((error) => { diff --git a/yarn.lock b/yarn.lock index e95dc24..a244268 100644 --- a/yarn.lock +++ b/yarn.lock @@ -296,6 +296,38 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@gitbeaker/core@^34.1.0": + version "34.1.0" + resolved "https://registry.yarnpkg.com/@gitbeaker/core/-/core-34.1.0.tgz#936101f0626c0548fc04e10baa42d0cb17b7b4ce" + integrity sha512-wus/m1KyVJWuT6GawLwbvAzkZiIW94oQAtALIY9YRUURzl1rDBbzIrsNvfc8BsmgvZx+TcRwOVBxZo68Tk5bBA== + dependencies: + "@gitbeaker/requester-utils" "^34.1.0" + form-data "^4.0.0" + li "^1.3.0" + mime-types "^2.1.32" + query-string "^7.0.0" + xcase "^2.0.1" + +"@gitbeaker/node@^34.1.0": + version "34.1.0" + resolved "https://registry.yarnpkg.com/@gitbeaker/node/-/node-34.1.0.tgz#733a8ba9a1a4f22fad959ba1e7f7ea768b998824" + integrity sha512-iRvwRiFf/V7KuQ4+enlvnfj9dmQSjVj4+m1j2Ic+19pp1CACaPLfPw5mL9I15qydfuL5WDGtwp4px02YcPopeg== + dependencies: + "@gitbeaker/core" "^34.1.0" + "@gitbeaker/requester-utils" "^34.1.0" + delay "^5.0.0" + got "^11.8.2" + xcase "^2.0.1" + +"@gitbeaker/requester-utils@^34.1.0": + version "34.1.0" + resolved "https://registry.yarnpkg.com/@gitbeaker/requester-utils/-/requester-utils-34.1.0.tgz#c96c1d3c20c26219aa5e7ebf4da45bb7defc760a" + integrity sha512-sN8f5ixlz1Vxoc79yMyS3h9Uwf2MW7Ru0JGfey2qaS/UkFK3Bfkv00iONeaC+2fbFGUMtQ9tl0FcbWrp4Zp0Jw== + dependencies: + form-data "^4.0.0" + qs "^6.10.1" + xcase "^2.0.1" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -623,6 +655,11 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== +"@sindresorhus/is@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.1.tgz#d26729db850fa327b7cacc5522252194404226f5" + integrity sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g== + "@sinonjs/commons@^1.7.0": version "1.8.1" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" @@ -644,6 +681,13 @@ dependencies: defer-to-connect "^1.0.1" +"@szmarczak/http-timer@^4.0.5": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== + dependencies: + defer-to-connect "^2.0.0" + "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": version "7.1.9" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d" @@ -677,6 +721,16 @@ dependencies: "@babel/types" "^7.3.0" +"@types/cacheable-request@^6.0.1": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" + integrity sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "*" + "@types/node" "*" + "@types/responselike" "*" + "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -697,6 +751,11 @@ dependencies: "@types/node" "*" +"@types/http-cache-semantics@*": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" + integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" @@ -734,6 +793,13 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== +"@types/keyv@*": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.2.tgz#5d97bb65526c20b6e0845f6b0d2ade4f28604ee5" + integrity sha512-/FvAK2p4jQOaJ6CGDHJTqZcUtbZe820qIeTg7o0Shg7drB4JHeL+V/dhSaly7NXx6u8eSee+r7coT+yuJEvDLg== + dependencies: + "@types/node" "*" + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -759,6 +825,13 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.0.2.tgz#5bb52ee68d0f8efa9cc0099920e56be6cc4e37f3" integrity sha512-IkVfat549ggtkZUthUzEX49562eGikhSYeVGX97SkMFn+sTZrgRewXjQ4tPKFPCykZHkX1Zfd9OoELGqKU2jJA== +"@types/responselike@*", "@types/responselike@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" + integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== + dependencies: + "@types/node" "*" + "@types/rimraf@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-3.0.0.tgz#b9d03f090ece263671898d57bb7bb007023ac19f" @@ -1240,6 +1313,11 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + cacheable-request@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" @@ -1253,6 +1331,27 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" +cacheable-request@^7.0.1: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" + integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1441,7 +1540,7 @@ colorspace@1.1.x: color "3.0.x" text-hex "1.0.x" -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -1600,6 +1699,13 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -1620,6 +1726,11 @@ defer-to-connect@^1.0.1: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" @@ -1642,6 +1753,11 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" +delay@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" + integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -2103,6 +2219,11 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +filter-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" + integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs= + find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -2139,6 +2260,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -2165,6 +2295,11 @@ fsevents@^2.1.2, fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" @@ -2180,6 +2315,15 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-intrinsic@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -2266,6 +2410,23 @@ globby@^11.0.1: merge2 "^1.3.0" slash "^3.0.0" +got@^11.8.2: + version "11.8.2" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" + integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.1" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -2316,6 +2477,11 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-symbols@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -2352,6 +2518,13 @@ has-yarn@^2.1.0: resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + hosted-git-info@^2.1.4: version "2.8.8" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" @@ -2383,6 +2556,14 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -3189,6 +3370,11 @@ json-buffer@3.0.0: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -3238,6 +3424,13 @@ keyv@^3.0.0: dependencies: json-buffer "3.0.0" +keyv@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" + integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== + dependencies: + json-buffer "3.0.1" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -3300,6 +3493,11 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +li@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/li/-/li-1.3.0.tgz#22c59bcaefaa9a8ef359cf759784e4bf106aea1b" + integrity sha1-IsWbyu+qmo7zWc91l4TkvxBq6hs= + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -3428,6 +3626,11 @@ mime-db@1.44.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== +mime-db@1.49.0: + version "1.49.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" + integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== + mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" @@ -3435,6 +3638,13 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "1.44.0" +mime-types@^2.1.32: + version "2.1.32" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" + integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== + dependencies: + mime-db "1.49.0" + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -3445,6 +3655,11 @@ mimic-response@^1.0.0, mimic-response@^1.0.1: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -3584,6 +3799,11 @@ normalize-url@^4.1.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -3617,6 +3837,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-inspect@^1.9.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" + integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== + object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -3681,6 +3906,11 @@ p-cancelable@^1.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== + p-each-series@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" @@ -3901,11 +4131,33 @@ pupa@^2.0.1: dependencies: escape-goat "^2.0.0" +qs@^6.10.1: + version "6.10.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" + integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== + dependencies: + side-channel "^1.0.4" + qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +query-string@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.0.1.tgz#45bd149cf586aaa582dffc7ec7a8ad97dd02f75d" + integrity sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA== + dependencies: + decode-uri-component "^0.2.0" + filter-obj "^1.1.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -4068,6 +4320,11 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +resolve-alpn@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -4104,6 +4361,13 @@ responselike@^1.0.2: dependencies: lowercase-keys "^1.0.0" +responselike@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" + integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== + dependencies: + lowercase-keys "^2.0.0" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -4253,6 +4517,15 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -4379,6 +4652,11 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== +split-on-first@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" + integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -4431,6 +4709,11 @@ stealthy-require@^1.1.1: resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= +strict-uri-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" + integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= + string-length@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1" @@ -5036,6 +5319,11 @@ ws@^7.2.3: resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== +xcase@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/xcase/-/xcase-2.0.1.tgz#c7fa72caa0f440db78fd5673432038ac984450b9" + integrity sha1-x/pyyqD0QNt4/VZzQyA4rJhEULk= + xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"