diff --git a/CHANGELOG.md b/CHANGELOG.md index 98ae97f01..c268a7592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ - Append random string to danger-results.json and danger-dsl.json files to better support concurrent processes #1311 - +- GitLab: Upgrade `@gitbreaker/node` from `^21.3.0` to `^^35.7.0` [#1319](https://github.com/danger/danger-js/pull/1319) [@ivankatliarchuk] - Gitlab package moved to a new home "@gitbreaker/*" [#1301](https://github.com/danger/danger-js/issues/1301) [@ivankatliarchuk] - GitLab: Improve support for MRs from forks [#1319](https://github.com/danger/danger-js/pull/1319) [@ivankatliarchuk] - GitLab: Added provider tests [#1319](https://github.com/danger/danger-js/pull/1319) [@ivankatliarchuk] diff --git a/package.json b/package.json index 62a62d78c..7c8ed807c 100644 --- a/package.json +++ b/package.json @@ -148,7 +148,8 @@ "debug": "^4.1.1", "fast-json-patch": "^3.0.0-1", "get-stdin": "^6.0.0", - "@gitbeaker/node": "^21.3.0", + "@gitbeaker/node": "^35.7.0", + "@gitbeaker/core": "^35.7.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.1", "hyperlinker": "^1.0.0", diff --git a/source/dsl/GitLabDSL.ts b/source/dsl/GitLabDSL.ts index 6f056e05c..2a60705f5 100644 --- a/source/dsl/GitLabDSL.ts +++ b/source/dsl/GitLabDSL.ts @@ -2,6 +2,19 @@ // TODO: extract out from BitBucket specifically, or create our own type import { Gitlab } from "@gitbeaker/node" import { RepoMetaData } from "./BitBucketServerDSL" +// some imports can be used as is. we can remove them +import { + MergeRequestSchema, + UpdateMergeRequestOptions, + MergeRequestLevelMergeRequestApprovalSchema, + MergeRequestNoteSchema, + BaseRequestOptions, + UserExtendedSchema, + RepositoryCompareSchema, + CommitDiffSchema, + CommitSchema, +} from "@gitbeaker/core/dist/types/types" +// ^ most of the imports can be used as is // getPlatformReviewDSLRepresentation export interface GitLabJSONDSL { @@ -27,133 +40,23 @@ export interface GitLabDSL extends GitLabJSONDSL { // --- // JSON responses from API -export interface GitLabUser { - id: number - name: string - username: string - state: "active" | "blocked" - avatar_url: string | null - web_url: string -} - -export interface GitLabUserProfile extends GitLabUser { - created_at: string - bio: string | null - location: string | null - public_email: string - skype: string - linkedin: string - twitter: string - website_url: string - organization: string - last_sign_in_at: string - confirmed_at: string - theme_id: number - last_activity_on: string - color_scheme_id: number - projects_limit: number - current_sign_in_at: string - identities: [{ provider: string; extern_uid: string }] - can_create_group: boolean - can_create_project: boolean - two_factor_enabled: boolean - external: boolean - private_profile: boolean -} - -export interface GitLabMRBase { - /** The MR's id */ - id: number - - /** The unique ID for this MR */ - iid: number - - /** The project ID for this MR */ - project_id: number - - /** The given name of the MR */ - title: string - - /** The body text describing the MR */ - description: string +// can be removed +export interface GitLabUserProfile extends UserExtendedSchema {} +export interface GitlabUpdateMr extends UpdateMergeRequestOptions, BaseRequestOptions {} +export interface GitLabApproval extends MergeRequestLevelMergeRequestApprovalSchema {} +export interface GitLabMRChange extends CommitDiffSchema {} +export interface GitLabRepositoryCompare extends RepositoryCompareSchema {} +export interface GitLabMRCommit extends CommitSchema {} +// ^ can be removed - /** The MR's current availability */ - state: "closed" | "open" | "locked" | "merged" - - /** When was the MR made */ - created_at: string - - /** When was the MR updated */ - updated_at: string - - /** What branch is this MR being merged into */ - target_branch: string - /** What branch is this MR come from */ - source_branch: string - - /** How many folks have given it an upvote */ - upvotes: number - /** How many folks have given it an downvote */ - downvotes: number - - /** Who made it */ - author: GitLabUser - /** Access rights for the user who created the MR */ - user: { - /** Does the author have access to merge? */ - can_merge: boolean - } - /** Who was assigned as the person to review */ - assignee?: GitLabUser - assignees: GitLabUser[] - /** Users who were added as reviewers to the MR */ - reviewers: GitLabUser[] - source_project_id: number - target_project_id: number - labels: string[] - work_in_progress: boolean - milestone: { - id: number - iid: number - project_id: number - title: string - description: string - state: "closed" | "active" - created_at: string - updated_at: string - due_date: string - start_date: string - web_url: string - } - merge_when_pipeline_succeeds: boolean - merge_status: "can_be_merged" // XXX: other statuses? - merge_error: null | null - sha: string - merge_commit_sha: string | null - user_notes_count: number - discussion_locked: null | null - should_remove_source_branch: boolean - force_remove_source_branch: boolean - allow_collaboration: boolean - allow_maintainer_to_push: boolean - web_url: string - time_stats: { - time_estimate: number - total_time_spent: number - human_time_estimate: number | null - human_total_time_spent: number | null - } +export interface GitLabInlineNote extends MergeRequestNoteSchema { + type: "DiffNote" | "DiscussionNote" | null // XXX: other types? null means "normal comment" } /** TODO: These need more comments from someone who uses GitLab, see GitLabDSL.ts in the danger-js repo */ -export interface GitLabMR extends GitLabMRBase { - squash: boolean +export interface GitLabMR extends MergeRequestSchema { subscribed: boolean changes_count: string - merged_by: GitLabUser - merged_at: string - closed_by: GitLabUser | null - closed_at: string | null latest_build_started_at: string latest_build_finished_at: string first_deployed_to_production_at: string | null @@ -171,37 +74,31 @@ export interface GitLabMR extends GitLabMRBase { } diverged_commits_count: number rebase_in_progress: boolean - approvals_before_merge: null | null -} - -export interface GitLabMRChange { - old_path: string - new_path: string - a_mode: string - b_mode: string - diff: string - new_file: boolean - renamed_file: boolean - deleted_file: boolean + approvals_before_merge: null + // + /** Access rights for the user who created the MR */ + user: { + /** Does the author have access to merge? */ + can_merge: boolean + } + merge_error: null + allow_collaboration: boolean + allow_maintainer_to_push: boolean } -export interface GitLabMRChanges extends GitLabMRBase { - changes: GitLabMRChange[] +export interface GitLabMRChanges extends MergeRequestSchema { + /** Access rights for the user who created the MR */ + user: { + /** Does the author have access to merge? */ + can_merge: boolean + } + merge_error: null + allow_collaboration: boolean + allow_maintainer_to_push: boolean } -export interface GitLabNote { - id: number +export interface GitLabNote extends MergeRequestNoteSchema { type: "DiffNote" | "DiscussionNote" | null // XXX: other types? null means "normal comment" - body: string - attachment: null // XXX: what can an attachment be? - author: GitLabUser - created_at: string - updated_at: string - system: boolean - noteable_id: number - noteable_type: "MergeRequest" // XXX: other types...? - resolvable: boolean - noteable_iid: number } export interface GitLabDiscussionTextPosition { @@ -214,70 +111,3 @@ export interface GitLabDiscussionTextPosition { old_path: string old_line: number | null } - -export interface GitLabInlineNote extends GitLabNote { - position: { - base_sha: string - start_sha: string - head_sha: string - old_path: string - new_path: string - position_type: "text" // XXX: other types? - old_line: number | null - new_line: number - } - resolvable: boolean - resolved: boolean - resolved_by: GitLabUser | null -} - -export interface GitLabMRCommit { - id: string - short_id: string - created_at: string - parent_ids: string[] - title: string - message: string - author_name: string - author_email: string - authored_date: string - committer_name: string - committer_email: string - committed_date: string -} - -export interface GitLabCommit { - id: string - short_id: string - title: string - author_name: string - author_email: string - created_at: string -} - -export interface GitLabRepositoryCompare { - commit: GitLabCommit - commits: GitLabCommit[] - diffs: GitLabMRChange[] - compare_timeout: boolean - compare_same_ref: boolean -} - -export interface GitLabApproval { - id: number - iid: number - project_id: number - title: string - description: string - state: "closed" | "open" | "locked" | "merged" - created_at: string - updated_at: string - merge_status: "can_be_merged" - approvals_required: number - approvals_left: number - approved_by?: - | { - user: GitLabUser - }[] - | GitLabUser[] -} diff --git a/source/platforms/GitLab.ts b/source/platforms/GitLab.ts index 90d776228..ed595f99a 100644 --- a/source/platforms/GitLab.ts +++ b/source/platforms/GitLab.ts @@ -36,21 +36,21 @@ class GitLab implements Platform { const changes = await this.api.getMergeRequestChanges() const commits = await this.api.getMergeRequestCommits() - const mappedCommits: GitCommit[] = commits.map(commit => { + const mappedCommits: GitCommit[] = commits.map((commit) => { return { sha: commit.id, author: { name: commit.author_name, - email: commit.author_email, - date: commit.authored_date, + email: commit.author_email as string, + date: (commit.authored_date as Date).toString(), }, committer: { - name: commit.committer_name, - email: commit.committer_email, - date: commit.committed_date, + name: commit.committer_name as string, + email: commit.committer_email as string, + date: (commit.committed_date as Date).toString(), }, message: commit.message, - parents: commit.parent_ids, + parents: commit.parent_ids as string[], url: `${this.api.projectURL}/commit/${commit.id}`, tree: null, } @@ -58,10 +58,10 @@ class GitLab implements Platform { // XXX: does "renamed_file"/move count is "delete/create", or "modified"? const modified_files: string[] = changes - .filter(change => !change.new_file && !change.deleted_file) - .map(change => change.new_path) - const created_files: string[] = changes.filter(change => change.new_file).map(change => change.new_path) - const deleted_files: string[] = changes.filter(change => change.deleted_file).map(change => change.new_path) + .filter((change) => !change.new_file && !change.deleted_file) + .map((change) => change.new_path) + const created_files: string[] = changes.filter((change) => change.new_file).map((change) => change.new_path) + const deleted_files: string[] = changes.filter((change) => change.deleted_file).map((change) => change.new_path) return { modified_files, @@ -74,7 +74,7 @@ class GitLab implements Platform { getInlineComments = async (dangerID: string): Promise => { const dangerUserID = (await this.api.getUser()).id - return (await this.api.getMergeRequestInlineNotes()).map(note => { + return (await this.api.getMergeRequestInlineNotes()).map((note) => { return { id: `${note.id}`, body: note.body, @@ -161,7 +161,6 @@ class GitLab implements Platform { d("deleteMainComment", { id: note.id }) await this.api.deleteMergeRequestNote(note.id) } - return notes.length > 0 } diff --git a/source/platforms/gitlab/GitLabAPI.ts b/source/platforms/gitlab/GitLabAPI.ts index 1448e4fe8..bc6437a83 100644 --- a/source/platforms/gitlab/GitLabAPI.ts +++ b/source/platforms/gitlab/GitLabAPI.ts @@ -10,13 +10,15 @@ import { GitLabNote, GitLabUserProfile, GitLabRepositoryCompare, + GitlabUpdateMr, GitLabApproval, } from "../../dsl/GitLabDSL" import { Gitlab } from "@gitbeaker/node" -import { RepositoryFileSchema } from "@gitbeaker/core/dist/types/services/RepositoryFiles" +import { DiscussionSchema, RepositoryFileExtendedSchema } from "@gitbeaker/core/dist/types/types" import { Env } from "../../ci_source/ci_source" import { debug } from "../../debug" +import { MergeRequestSchema } from "@gitbeaker/core/dist/types/resources/MergeRequests" export type GitLabAPIToken = string export type GitLabOAuthToken = string @@ -37,7 +39,7 @@ export function getGitLabAPICredentialsFromEnv(env: Env): GitLabAPICredentials { const protocolRegex = /^https?:\/\//i host = protocolRegex.test(envHost) ? envHost : `https://${envHost}` } else if (envCIAPI) { - // GitLab >= v11.7 supplies the API Endpoint in an environment variable and we can work out our host value from that. + // GitLab >= v11.7 supplies the API Endpoint in an environment variable, and we can work out our host value from that. // See https://docs.gitlab.com/ce/ci/variables/predefined_variables.html const hostRegex = /^(https?):\/\/([^\/]+)\//i if (hostRegex.test(envCIAPI)) { @@ -57,22 +59,26 @@ export function getGitLabAPICredentialsFromEnv(env: Env): GitLabAPICredentials { class GitLabAPI { fetch: typeof fetch - private api: InstanceType + private readonly api: InstanceType private readonly hostURL: string private readonly d = debug("GitLabAPI") + private readonly repoSlug: string + private pullRequestID: number constructor(public readonly repoMetadata: RepoMetaData, public readonly repoCredentials: GitLabAPICredentials) { this.fetch = fetch this.api = new Gitlab(repoCredentials) this.hostURL = repoCredentials.host + this.repoSlug = repoMetadata.repoSlug + this.pullRequestID = Number(repoMetadata.pullRequestID) } get projectURL(): string { - return `${this.hostURL}/${this.repoMetadata.repoSlug}` + return `${this.hostURL}/${this.repoSlug}` } get mergeRequestURL(): string { - return `${this.projectURL}/merge_requests/${this.repoMetadata.pullRequestID}` + return `${this.projectURL}/merge_requests/${this.pullRequestID}` } get apiInstance() { @@ -87,62 +93,49 @@ class GitLabAPI { } getMergeRequestInfo = async (): Promise => { - this.d(`getMergeRequestInfo for repo: ${this.repoMetadata.repoSlug} pr: ${this.repoMetadata.pullRequestID}`) - const mr = (await this.api.MergeRequests.show( - this.repoMetadata.repoSlug, - Number(this.repoMetadata.pullRequestID) - )) as GitLabMR + this.d(`getMergeRequestInfo for repo: ${this.repoSlug} pr: ${this.pullRequestID}`) + const mr = (await this.api.MergeRequests.show(this.repoSlug, this.pullRequestID)) as GitLabMR this.d("getMergeRequestInfo", mr) return mr } - updateMergeRequestInfo = async (changes: object): Promise => { - const mr = this.api.MergeRequests.edit(this.repoMetadata.repoSlug, Number(this.repoMetadata.pullRequestID), changes) - + updateMergeRequestInfo = async (changes: GitlabUpdateMr): Promise => { + const mr = this.api.MergeRequests.edit(this.repoSlug, this.pullRequestID, changes) this.d("updateMergeRequestInfo", mr) - return mr } getMergeRequestApprovals = async (): Promise => { - this.d(`getMergeRequestApprovals for repo: ${this.repoMetadata.repoSlug} pr: ${this.repoMetadata.pullRequestID}`) - const approvals = (await this.api.MergeRequests.approvals(this.repoMetadata.repoSlug, { - mergerequestIid: Number(this.repoMetadata.pullRequestID), + this.d(`getMergeRequestApprovals for repo: ${this.repoSlug} pr: ${this.pullRequestID}`) + const approvals = (await this.api.MergeRequestApprovals.configuration(this.repoSlug, { + mergerequestIid: this.pullRequestID, })) as GitLabApproval this.d("getMergeRequestApprovals", approvals) return approvals } getMergeRequestChanges = async (): Promise => { - this.d(`getMergeRequestChanges for repo: ${this.repoMetadata.repoSlug} pr: ${this.repoMetadata.pullRequestID}`) - const mr = (await this.api.MergeRequests.changes( - this.repoMetadata.repoSlug, - Number(this.repoMetadata.pullRequestID) - )) as GitLabMRChanges - + this.d(`getMergeRequestChanges for repo: ${this.repoSlug} pr: ${this.pullRequestID}`) + const mr = (await this.api.MergeRequests.changes(this.repoSlug, this.pullRequestID)) as GitLabMRChanges this.d("getMergeRequestChanges", mr.changes) - return mr.changes + return mr.changes as GitLabMRChange[] } getMergeRequestCommits = async (): Promise => { this.d("getMergeRequestCommits", this.repoMetadata.repoSlug, this.repoMetadata.pullRequestID) - const commits = (await this.api.MergeRequests.commits( - this.repoMetadata.repoSlug, - Number(this.repoMetadata.pullRequestID) - )) as GitLabMRCommit[] + const commits = (await this.api.MergeRequests.commits(this.repoSlug, this.pullRequestID)) as GitLabMRCommit[] this.d("getMergeRequestCommits", commits) return commits } getMergeRequestNotes = async (): Promise => { this.d("getMergeRequestNotes", this.repoMetadata.repoSlug, this.repoMetadata.pullRequestID) - const api = this.api.MergeRequestNotes - const notes = (await api.all(this.repoMetadata.repoSlug, this.repoMetadata.pullRequestID, {})) as GitLabNote[] + const notes = (await this.api.MergeRequestNotes.all(this.repoSlug, this.pullRequestID, {})) as GitLabNote[] this.d("getMergeRequestNotes", notes) return notes } - getMergeRequestInlineNotes = async (): Promise => { + getMergeRequestInlineNotes = async (): Promise => { this.d("getMergeRequestInlineNotes") const notes: GitLabNote[] = await this.getMergeRequestNotes() const inlineNotes = notes.filter((note: GitLabNote) => note.type == "DiffNote") as GitLabInlineNote[] @@ -151,19 +144,11 @@ class GitLabAPI { } createMergeRequestDiscussion = async (content: string, position: GitLabDiscussionTextPosition): Promise => { - this.d( - "createMergeRequestDiscussion", - this.repoMetadata.repoSlug, - this.repoMetadata.pullRequestID, - content, - position - ) - const api = this.api.MergeRequestDiscussions - + this.d("createMergeRequestDiscussion", this.repoSlug, this.pullRequestID, content, position) try { - const result = await api.create(this.repoMetadata.repoSlug, this.repoMetadata.pullRequestID, content, { + const result = (await this.api.MergeRequestDiscussions.create(this.repoSlug, this.pullRequestID, content, { position: position, - }) + })) as DiscussionSchema this.d("createMergeRequestDiscussion", result) return result as unknown as string // not sure why? } catch (e) { @@ -173,42 +158,35 @@ class GitLabAPI { } createMergeRequestNote = async (body: string): Promise => { - this.d("createMergeRequestNote", this.repoMetadata.repoSlug, this.repoMetadata.pullRequestID, body) + this.d("createMergeRequestNote", this.repoSlug, this.pullRequestID, body) const api = this.api.MergeRequestNotes - try { - this.d("createMergeRequestNote") - const note = (await api.create(this.repoMetadata.repoSlug, this.repoMetadata.pullRequestID, body)) as GitLabNote + const note = (await api.create(this.repoSlug, this.pullRequestID, body)) as GitLabNote this.d("createMergeRequestNote", note) return note } catch (e) { this.d("createMergeRequestNote", e) } - return Promise.reject() } updateMergeRequestNote = async (id: number, body: string): Promise => { - this.d("updateMergeRequestNote", this.repoMetadata.repoSlug, this.repoMetadata.pullRequestID, id, body) - const api = this.api.MergeRequestNotes + this.d("updateMergeRequestNote", this.repoSlug, this.pullRequestID, id, body) try { - const note = (await api.edit(this.repoMetadata.repoSlug, this.repoMetadata.pullRequestID, id, body)) as GitLabNote + const note = (await this.api.MergeRequestNotes.edit(this.repoSlug, this.pullRequestID, id, body)) as GitLabNote this.d("updateMergeRequestNote", note) return note } catch (e) { this.d("updateMergeRequestNote", e) + return Promise.reject() } - - return Promise.reject() } // note: deleting the _only_ note in a discussion also deletes the discussion \o/ deleteMergeRequestNote = async (id: number): Promise => { - this.d("deleteMergeRequestNote", this.repoMetadata.repoSlug, this.repoMetadata.pullRequestID, id) - const api = this.api.MergeRequestNotes - + this.d("deleteMergeRequestNote", this.repoSlug, this.pullRequestID, id) try { - await api.remove(this.repoMetadata.repoSlug, this.repoMetadata.pullRequestID, id) + await this.api.MergeRequestNotes.remove(this.repoSlug, this.pullRequestID, id) this.d("deleteMergeRequestNote", true) return true } catch (e) { @@ -219,8 +197,7 @@ class GitLabAPI { getFileContents = async (path: string, slug?: string, ref?: string): Promise => { this.d(`getFileContents requested for path:${path}, slug:${slug}, ref:${ref}`) - const api = this.api.RepositoryFiles - const projectId = slug || this.repoMetadata.repoSlug + const projectId = slug || this.repoSlug // Use the current state of PR if no ref is passed if (!ref) { const mr: GitLabMR = await this.getMergeRequestInfo() @@ -229,7 +206,7 @@ class GitLabAPI { try { this.d("getFileContents", projectId, path, ref) - const response = (await api.show(projectId, path, ref)) as RepositoryFileSchema + const response = (await this.api.RepositoryFiles.show(projectId, path, ref)) as RepositoryFileExtendedSchema const result: string = Buffer.from(response.content, response.encoding).toString() this.d("getFileContents", result) return result @@ -243,22 +220,18 @@ class GitLabAPI { } } - getCompareChanges = async (base?: string, head?: string): Promise => { + getCompareChanges = async (base?: string, head?: string): Promise => { if (!base || !head) { return this.getMergeRequestChanges() } - const api = this.api.Repositories - const projectId = this.repoMetadata.repoSlug - const compare = (await api.compare(projectId, base, head)) as GitLabRepositoryCompare + const compare = (await this.api.Repositories.compare(this.repoSlug, base, head)) as GitLabRepositoryCompare return compare.diffs } addLabels = async (...labels: string[]): Promise => { const mr = await this.getMergeRequestInfo() - mr.labels.push(...labels) - - await this.updateMergeRequestInfo({ labels: mr.labels.join(",") }) - + const noDuplicates = new Set([...(mr.labels as string[]), ...labels]) + await this.updateMergeRequestInfo({ labels: Array.from(noDuplicates).join(",") }) return true } @@ -266,14 +239,12 @@ class GitLabAPI { const mr = await this.getMergeRequestInfo() for (const label of labels) { - const index = mr.labels.indexOf(label) - if (index > -1) { - mr.labels.splice(index, 1) + const index = mr.labels?.indexOf(label) + if ((index as number) > -1) { + mr.labels?.splice(index as number, 1) } } - - await this.updateMergeRequestInfo({ labels: mr.labels.join(",") }) - + await this.updateMergeRequestInfo({ labels: mr.labels?.join(",") }) return true } } diff --git a/source/platforms/gitlab/GitLabGit.ts b/source/platforms/gitlab/GitLabGit.ts index 6f0bf8e7f..95c868959 100644 --- a/source/platforms/gitlab/GitLabGit.ts +++ b/source/platforms/gitlab/GitLabGit.ts @@ -14,7 +14,7 @@ export const gitLabGitDSL = (gitlab: GitLabDSL, json: GitJSONDSL, gitlabAPI: Git getFileContents: gitlab.utils.fileContents, getFullDiff: async (base: string, head: string) => { const changes = await gitlabAPI.getCompareChanges(base, head) - return gitlabChangesToDiff(changes) + return gitlabChangesToDiff(changes as GitLabMRChange[]) }, } @@ -26,7 +26,7 @@ export const gitlabChangesToDiff = (changes: GitLabMRChange[]): string => { d("Converting GitLab Changes to Diff") // Gitlab doesn't return full raw git diff, relevant issue: https://gitlab.com/gitlab-org/gitlab/issues/24913 return changes - .map(change => { + .map((change) => { const { diff } = change if (diff.startsWith("diff --git a/") || diff.startsWith("--- a/") || diff.startsWith("--- /dev/null")) { return diff diff --git a/source/platforms/gitlab/_tests/_gitlab_api.test.ts b/source/platforms/gitlab/_tests/_gitlab_api.test.ts index 5f6cd4011..253df8319 100644 --- a/source/platforms/gitlab/_tests/_gitlab_api.test.ts +++ b/source/platforms/gitlab/_tests/_gitlab_api.test.ts @@ -187,4 +187,38 @@ describe("GitLab API", () => { } nockDone() }) + + it("updateMergeRequestInfo", async () => { + const { nockDone } = await nockBack("updateMergeRequestInfo.json") + const titleToUpdate = "update merge request" + const result = await api.updateMergeRequestInfo({ title: titleToUpdate }) + nockDone() + expect(result.title).toEqual(titleToUpdate) + }) + + describe("mergerequest (add|remove)labels", () => { + let nockDone: { nockDone: () => void } + + afterAll(async () => { + nockDone.nockDone() + }) + + it("addLabels", async () => { + nockDone = await nockBack("updateMergeRequestInfo.json") + const result = await api.addLabels("first-label", "second-label") + expect(result).toBeTruthy() + }) + + it("removeLabels", async () => { + nockDone = await nockBack("updateMergeRequestInfo.json") + const result = await api.removeLabels("remove-me-label") + expect(result).toBeTruthy() + }) + + it("addLabels with no duplicates", async () => { + nockDone = await nockBack("updateMergeRequestInfo.json") + const result = await api.addLabels("danger-bot", "new-label") + expect(result).toBeTruthy() + }) + }) }) diff --git a/source/platforms/gitlab/_tests/fixtures/updateMergeRequestInfo.json b/source/platforms/gitlab/_tests/fixtures/updateMergeRequestInfo.json new file mode 100644 index 000000000..ed7a5a261 --- /dev/null +++ b/source/platforms/gitlab/_tests/fixtures/updateMergeRequestInfo.json @@ -0,0 +1,341 @@ +[ + { + "scope": "https://gitlab.com:443", + "method": "PUT", + "path": "/api/v4/projects/gitlab-org%2Fgitlab-foss/merge_requests/27117", + "body": "{\"title\":\"update merge request\"}", + "status": 201, + "reqheaders": { + "user-agent": "gitbeaker", + "private-token": "FAKE_DANGER_GITLAB_API_TOKEN", + "content-type": "application/json", + "content-length": "32", + "accept-encoding": "gzip, deflate, br" + }, + "response": { + "id": 27253868, + "iid": 27117, + "project_id": 13083, + "title": "update merge request", + "description": "merge request should have being updated with api", + "state": "merged", + "created_at": "2019-04-08T10:59:38.140Z", + "updated_at": "2019-05-02T14:34:54.068Z", + "merged_by": { + "id": 283999, + "name": "Douglas Barbosa Alexandre", + "username": "dbalexandre", + "state": "active", + "avatar_url": "https://gl-canary.freetls.fastly.net/uploads/-/system/user/avatar/283999/avatar.png", + "web_url": "https://gitlab.com/dbalexandre" + }, + "merged_at": "2019-04-09T13:57:11.931Z", + "closed_by": null, + "closed_at": null, + "target_branch": "master", + "source_branch": "stable-reviewer-roulette", + "user_notes_count": 4, + "upvotes": 3, + "downvotes": 0, + "assignee": { + "id": 283999, + "name": "Douglas Barbosa Alexandre", + "username": "dbalexandre", + "state": "active", + "avatar_url": "https://gl-canary.freetls.fastly.net/uploads/-/system/user/avatar/283999/avatar.png", + "web_url": "https://gitlab.com/dbalexandre" + }, + "author": { + "id": 443319, + "name": "Sean McGivern", + "username": "smcgivern", + "state": "active", + "avatar_url": "https://gl-canary.freetls.fastly.net/uploads/-/system/user/avatar/443319/avatar.png", + "web_url": "https://gitlab.com/smcgivern" + }, + "assignees": [ + { + "id": 283999, + "name": "Douglas Barbosa Alexandre", + "username": "dbalexandre", + "state": "active", + "avatar_url": "https://gl-canary.freetls.fastly.net/uploads/-/system/user/avatar/283999/avatar.png", + "web_url": "https://gitlab.com/dbalexandre" + } + ], + "source_project_id": 13083, + "target_project_id": 13083, + "labels": ["danger-bot", "Plan", "backend", "backstage", "remove-me-label"], + "work_in_progress": false, + "merge_when_pipeline_succeeds": false, + "merge_status": "can_be_merged", + "sha": "28531ab43666b5fdf37e0a70db3bcbf7d3f92183", + "merge_commit_sha": "58d4099c1469dba9ff850733ba29da11f6eeb158", + "discussion_locked": null, + "should_remove_source_branch": null, + "force_remove_source_branch": true, + "reference": "!27117", + "web_url": "https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/27117", + "squash": false, + "subscribed": false, + "changes_count": "1", + "latest_build_started_at": "2019-04-08T10:56:58.249Z", + "latest_build_finished_at": "2019-04-08T11:56:52.871Z", + "first_deployed_to_production_at": null, + "diff_refs": { + "base_sha": "50cd5d9b776848cf23f1fd1ec52789dbdf946185", + "head_sha": "28531ab43666b5fdf37e0a70db3bcbf7d3f92183", + "start_sha": "50cd5d9b776848cf23f1fd1ec52789dbdf946185" + }, + "merge_error": null, + "approvals_before_merge": 1 + }, + "rawHeaders": ["Server", "nginx"] + }, + { + "scope": "https://gitlab.com:443", + "method": "GET", + "path": "/api/v4/projects/gitlab-org%2Fgitlab-foss/merge_requests/27117", + "body": "", + "status": 200, + "response": { + "id": 27253868, + "iid": 27117, + "project_id": 13083, + "title": "Stable reviewer roulette", + "description": "some description", + "state": "merged", + "created_at": "2019-04-08T10:59:38.140Z", + "updated_at": "2019-05-02T14:34:54.068Z", + "merged_by": { + "id": 283999, + "name": "Douglas Barbosa Alexandre", + "username": "dbalexandre", + "state": "active", + "avatar_url": "https://gl-canary.freetls.fastly.net/uploads/-/system/user/avatar/283999/avatar.png", + "web_url": "https://gitlab.com/dbalexandre" + }, + "merged_at": "2019-04-09T13:57:11.931Z", + "closed_by": null, + "closed_at": null, + "target_branch": "master", + "source_branch": "stable-reviewer-roulette", + "user_notes_count": 4, + "upvotes": 3, + "downvotes": 0, + "assignee": { + "id": 283999, + "name": "Douglas Barbosa Alexandre", + "username": "dbalexandre", + "state": "active", + "avatar_url": "https://gl-canary.freetls.fastly.net/uploads/-/system/user/avatar/283999/avatar.png", + "web_url": "https://gitlab.com/dbalexandre" + }, + "source_project_id": 13083, + "target_project_id": 13083, + "labels": ["danger-bot", "Plan", "backend", "backstage"], + "work_in_progress": false, + "milestone": { + "id": 655280, + "iid": 28, + "group_id": 9970, + "title": "11.11", + "description": "", + "state": "active", + "created_at": "2018-09-21T19:08:37.027Z", + "updated_at": "2019-01-16T19:48:19.411Z", + "due_date": "2019-05-22", + "start_date": "2019-04-08", + "web_url": "https://gitlab.com/groups/gitlab-org/-/milestones/28" + }, + "merge_when_pipeline_succeeds": false, + "merge_status": "can_be_merged", + "sha": "28531ab43666b5fdf37e0a70db3bcbf7d3f92183", + "merge_commit_sha": "58d4099c1469dba9ff850733ba29da11f6eeb158", + "discussion_locked": null, + "should_remove_source_branch": null, + "force_remove_source_branch": true, + "reference": "!27117", + "web_url": "https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/27117", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + }, + "squash": false, + "subscribed": false, + "changes_count": "1", + "latest_build_started_at": "2019-04-08T10:56:58.249Z", + "latest_build_finished_at": "2019-04-08T11:56:52.871Z", + "first_deployed_to_production_at": null, + "diff_refs": { + "base_sha": "50cd5d9b776848cf23f1fd1ec52789dbdf946185", + "head_sha": "28531ab43666b5fdf37e0a70db3bcbf7d3f92183", + "start_sha": "50cd5d9b776848cf23f1fd1ec52789dbdf946185" + }, + "merge_error": null, + "user": { + "can_merge": false + }, + "approvals_before_merge": 1 + }, + "rawHeaders": ["Server", "nginx"] + }, + { + "scope": "https://gitlab.com:443", + "method": "PUT", + "path": "/api/v4/projects/gitlab-org%2Fgitlab-foss/merge_requests/27117", + "body": "{\"labels\":\"danger-bot,Plan,backend,backstage,first-label,second-label\"}", + "status": 201, + "reqheaders": {}, + "response": { + "id": 27253868, + "iid": 27117, + "project_id": 13083, + "title": "update merge request", + "description": "merge request. update labels", + "state": "merged", + "created_at": "2019-04-08T10:59:38.140Z", + "updated_at": "2019-05-02T14:34:54.068Z", + "merged_by": { + "id": 283999, + "name": "Douglas Barbosa Alexandre", + "username": "dbalexandre", + "state": "active", + "avatar_url": "https://gl-canary.freetls.fastly.net/uploads/-/system/user/avatar/283999/avatar.png", + "web_url": "https://gitlab.com/dbalexandre" + }, + "merged_at": "2019-04-09T13:57:11.931Z", + "closed_by": null, + "closed_at": null, + "target_branch": "master", + "source_branch": "stable-reviewer-roulette", + "user_notes_count": 4, + "upvotes": 3, + "downvotes": 0, + "source_project_id": 13083, + "target_project_id": 13083, + "labels": ["danger-bot", "Plan", "backend", "backstage", "first-label", "second-label"], + "work_in_progress": false, + "merge_when_pipeline_succeeds": false, + "merge_status": "can_be_merged", + "sha": "28531ab43666b5fdf37e0a70db3bcbf7d3f92183", + "merge_commit_sha": "58d4099c1469dba9ff850733ba29da11f6eeb158", + "discussion_locked": null, + "should_remove_source_branch": null, + "force_remove_source_branch": true, + "reference": "!27117", + "web_url": "https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/27117", + "squash": false, + "subscribed": false, + "changes_count": "1", + "latest_build_started_at": "2019-04-08T10:56:58.249Z", + "latest_build_finished_at": "2019-04-08T11:56:52.871Z", + "first_deployed_to_production_at": null, + "diff_refs": { + "base_sha": "50cd5d9b776848cf23f1fd1ec52789dbdf946185", + "head_sha": "28531ab43666b5fdf37e0a70db3bcbf7d3f92183", + "start_sha": "50cd5d9b776848cf23f1fd1ec52789dbdf946185" + }, + "merge_error": null, + "user": { + "can_merge": false + }, + "approvals_before_merge": 1 + }, + "rawHeaders": ["Server", "nginx"] + }, + { + "scope": "https://gitlab.com:443", + "method": "PUT", + "path": "/api/v4/projects/gitlab-org%2Fgitlab-foss/merge_requests/27117", + "body": "{\"labels\":\"danger-bot,Plan,backend,backstage\"}", + "status": 201, + "reqheaders": {}, + "response": { + "id": 27253868, + "iid": 27117, + "project_id": 13083, + "title": "update merge request", + "description": "merge request. update labels", + "state": "merged", + "created_at": "2019-04-08T10:59:38.140Z", + "updated_at": "2019-05-02T14:34:54.068Z", + "labels": ["danger-bot", "Plan", "backend", "backstage"], + "work_in_progress": false, + "merge_when_pipeline_succeeds": false, + "merge_status": "can_be_merged", + "sha": "28531ab43666b5fdf37e0a70db3bcbf7d3f92183", + "merge_commit_sha": "58d4099c1469dba9ff850733ba29da11f6eeb158", + "discussion_locked": null, + "should_remove_source_branch": null, + "force_remove_source_branch": true, + "reference": "!27117", + "web_url": "https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/27117", + "squash": false, + "subscribed": false, + "changes_count": "1", + "latest_build_started_at": "2019-04-08T10:56:58.249Z", + "latest_build_finished_at": "2019-04-08T11:56:52.871Z", + "first_deployed_to_production_at": null, + "diff_refs": { + "base_sha": "50cd5d9b776848cf23f1fd1ec52789dbdf946185", + "head_sha": "28531ab43666b5fdf37e0a70db3bcbf7d3f92183", + "start_sha": "50cd5d9b776848cf23f1fd1ec52789dbdf946185" + }, + "merge_error": null, + "user": { + "can_merge": false + }, + "approvals_before_merge": 1 + }, + "rawHeaders": ["Server", "nginx"] + }, + { + "scope": "https://gitlab.com:443", + "method": "PUT", + "path": "/api/v4/projects/gitlab-org%2Fgitlab-foss/merge_requests/27117", + "body": "{\"labels\":\"danger-bot,Plan,backend,backstage,new-label\"}", + "status": 201, + "reqheaders": {}, + "response": { + "id": 27253868, + "iid": 27117, + "project_id": 13083, + "title": "update merge request", + "description": "merge request. update labels", + "state": "merged", + "created_at": "2019-04-08T10:59:38.140Z", + "updated_at": "2019-05-02T14:34:54.068Z", + "labels": ["danger-bot", "Plan", "backend", "backstage", "new-label"], + "work_in_progress": false, + "merge_when_pipeline_succeeds": false, + "merge_status": "can_be_merged", + "sha": "28531ab43666b5fdf37e0a70db3bcbf7d3f92183", + "merge_commit_sha": "58d4099c1469dba9ff850733ba29da11f6eeb158", + "discussion_locked": null, + "should_remove_source_branch": null, + "force_remove_source_branch": true, + "reference": "!27117", + "web_url": "https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/27117", + "squash": false, + "subscribed": false, + "changes_count": "1", + "latest_build_started_at": "2019-04-08T10:56:58.249Z", + "latest_build_finished_at": "2019-04-08T11:56:52.871Z", + "first_deployed_to_production_at": null, + "diff_refs": { + "base_sha": "50cd5d9b776848cf23f1fd1ec52789dbdf946185", + "head_sha": "28531ab43666b5fdf37e0a70db3bcbf7d3f92183", + "start_sha": "50cd5d9b776848cf23f1fd1ec52789dbdf946185" + }, + "merge_error": null, + "user": { + "can_merge": false + }, + "approvals_before_merge": 1 + }, + "rawHeaders": ["Server", "nginx"] + } +] diff --git a/yarn.lock b/yarn.lock index 482298f2d..67fdcb3b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1186,34 +1186,36 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@gitbeaker/core@^21.7.0": - version "21.7.0" - resolved "https://registry.yarnpkg.com/@gitbeaker/core/-/core-21.7.0.tgz#fcf7a12915d39f416e3f316d0a447a814179b8e5" - integrity sha512-cw72rE7tA27wc6JJe1WqeAj9v/6w0S7XJcEji+bRNjTlUfE1zgfW0Gf1mbGUi7F37SOABGCosQLfg9Qe63aIqA== +"@gitbeaker/core@^35.7.0": + version "35.7.0" + resolved "https://registry.yarnpkg.com/@gitbeaker/core/-/core-35.7.0.tgz#426e426dff597c1d094bf1fe66fb1109980e816d" + integrity sha512-1N9QcHElYa1NuLhX9mJJ6tnL7wbCsK8Naj2kLXwNC4qyEcDhMiJDnI3YoqNIXSzPTufoNUAbgIsc/h/JmO17/A== dependencies: - "@gitbeaker/requester-utils" "^21.7.0" - form-data "^3.0.0" + "@gitbeaker/requester-utils" "^35.7.0" + form-data "^4.0.0" li "^1.3.0" + mime "^3.0.0" + query-string "^7.0.0" xcase "^2.0.1" -"@gitbeaker/node@^21.3.0": - version "21.7.0" - resolved "https://registry.yarnpkg.com/@gitbeaker/node/-/node-21.7.0.tgz#2c19613f44ee497a8808c555abec614ebd2dfcad" - integrity sha512-OdM3VcTKYYqboOsnbiPcO0XimXXpYK4gTjARBZ6BWc+1LQXKmqo+OH6oUbyxOoaFu9hHECafIt3WZU3NM4sZTg== +"@gitbeaker/node@^35.7.0": + version "35.7.0" + resolved "https://registry.yarnpkg.com/@gitbeaker/node/-/node-35.7.0.tgz#ba4ece7aff388132bdcab8d6fb08c8d063a70e7d" + integrity sha512-zh215EUloAxj2gwTHevBVypEiiwQR0WsFLGPWJwY+yUFJVQRcya+3mcsDbxgCLAk00wwhrTVYyNppvmoYbEZNg== dependencies: - "@gitbeaker/core" "^21.7.0" - "@gitbeaker/requester-utils" "^21.7.0" - form-data "^3.0.0" - got "^11.1.4" + "@gitbeaker/core" "^35.7.0" + "@gitbeaker/requester-utils" "^35.7.0" + delay "^5.0.0" + got "^11.8.3" xcase "^2.0.1" -"@gitbeaker/requester-utils@^21.7.0": - version "21.7.0" - resolved "https://registry.yarnpkg.com/@gitbeaker/requester-utils/-/requester-utils-21.7.0.tgz#e9a9cfaf268d2a99eb7bbdc930943240a5f88878" - integrity sha512-eLTaVXlBnh8Qimj6QuMMA06mu/mLcJm3dy8nqhhn/Vm/D25sPrvpGwmbfFyvzj6QujPqtHvFfsCHtyZddL01qA== +"@gitbeaker/requester-utils@^35.7.0": + version "35.7.0" + resolved "https://registry.yarnpkg.com/@gitbeaker/requester-utils/-/requester-utils-35.7.0.tgz#7c1ded70a10ac890322f22488ffea8a3a41ccc79" + integrity sha512-SDYKhL+XUrslpVwUumkCf4I4Ubf+lvzdghCYPwBt/og5kZIorFVbHCxRmtr5bO+iC9nrVNfg24sdoe51vDGn1w== dependencies: - form-data "^3.0.0" - query-string "^6.12.1" + form-data "^4.0.0" + qs "^6.10.1" xcase "^2.0.1" "@humanwhocodes/config-array@^0.9.2": @@ -3434,6 +3436,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" @@ -4381,6 +4388,15 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +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.1: version "2.3.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" @@ -4728,7 +4744,7 @@ got@10.7.0: to-readable-stream "^2.0.0" type-fest "^0.10.0" -got@^11.1.4: +got@^11.8.3: version "11.8.5" resolved "https://registry.yarnpkg.com/got/-/got-11.8.5.tgz#ce77d045136de56e8f024bebb82ea349bc730046" integrity sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ== @@ -6859,6 +6875,11 @@ mime@1.3.4: resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" integrity sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM= +mime@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" + integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== + mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" @@ -7209,6 +7230,11 @@ object-inspect@^1.12.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== +object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + object-keys@^1.0.8: version "1.0.11" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" @@ -7966,15 +7992,22 @@ pupa@^2.0.1: dependencies: escape-goat "^2.0.0" +qs@^6.10.1: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A== -query-string@^6.12.1: - version "6.14.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a" - integrity sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw== +query-string@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.1.tgz#754620669db978625a90f635f12617c271a088e1" + integrity sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w== dependencies: decode-uri-component "^0.2.0" filter-obj "^1.1.0" @@ -8798,6 +8831,15 @@ shx@^0.3.4: minimist "^1.2.3" shelljs "^0.8.5" +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.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"