diff --git a/Dangerfile.ts b/Dangerfile.ts index ee40be811e8..1b7979adc54 100644 --- a/Dangerfile.ts +++ b/Dangerfile.ts @@ -1,9 +1,9 @@ // Import custom DangerJS rules. // See http://danger.systems/js import { DangerDSLType } from "danger/distribution/dsl/DangerDSL"; -import { commentPrWithTicketsInfo } from "./scripts/ts/danger/commentPrWithTicketsInfo"; -import { updatePrTitleForChangelog } from "./scripts/ts/danger/updatePrTitleForChangelog"; -import { getTicketsFromTitle } from "./scripts/ts/danger/utils/titleParser"; +import { commentPrWithTicketsInfo } from "./scripts/ts/danger/commentPr"; +import { updatePrTitleForChangelog } from "./scripts/ts/danger/updatePrTitle"; +import { getTicketsFromTitle } from "./scripts/ts/common/jiraTicket"; declare const danger: DangerDSLType; diff --git a/package.json b/package.json index f0fbe566344..0206c077df8 100644 --- a/package.json +++ b/package.json @@ -264,7 +264,6 @@ "babel-preset-react-native": "^4.0.1", "chalk": "^2.4.1", "danger": "^10.3.0", - "danger-plugin-digitalcitizenship": "^0.3.1", "eslint": "^8.6.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-functional": "^4.1.1", @@ -286,7 +285,6 @@ "node-fetch": "^2.6.7", "npm-run-all": "^4.1.3", "patch-package": "6.5.1", - "pivotaljs": "^1.0.3", "plist": "^3.0.5", "postinstall-postinstall": "^1.0.0", "prettier": "2.8.8", @@ -320,7 +318,7 @@ }, "standard-version": { "scripts": { - "postchangelog": "node scripts/changelog/add_pivotal_stories.js" + "postchangelog": "node scripts/changelog/add_jira_stories.js" } } } diff --git a/patches/danger-plugin-digitalcitizenship+0.3.1.patch b/patches/danger-plugin-digitalcitizenship+0.3.1.patch deleted file mode 100644 index 6da95378c8f..00000000000 --- a/patches/danger-plugin-digitalcitizenship+0.3.1.patch +++ /dev/null @@ -1,26 +0,0 @@ -diff --git a/node_modules/danger-plugin-digitalcitizenship/dist/utils.js b/node_modules/danger-plugin-digitalcitizenship/dist/utils.js -index 82e1d17..3d9a783 100644 ---- a/node_modules/danger-plugin-digitalcitizenship/dist/utils.js -+++ b/node_modules/danger-plugin-digitalcitizenship/dist/utils.js -@@ -6,7 +6,7 @@ const Pivotal = require("pivotaljs"); - * see https://www.pivotaltracker.com/help/articles/githubs_service_hook_for_tracker/ - */ - function getPivotalStoryIDs(message) { -- const matches = message.match(/^\[(#\d+(,#\d+)*)\]\s.+/); -+ const matches = message.match(/\[(#\d+(,#\d+)*)\]\s.+/); - if (matches) { - return matches[1] - .split(",") -diff --git a/node_modules/danger-plugin-digitalcitizenship/src/utils.ts b/node_modules/danger-plugin-digitalcitizenship/src/utils.ts -index 5037cb8..7e815d9 100644 ---- a/node_modules/danger-plugin-digitalcitizenship/src/utils.ts -+++ b/node_modules/danger-plugin-digitalcitizenship/src/utils.ts -@@ -5,7 +5,7 @@ import Pivotal = require("pivotaljs"); - * see https://www.pivotaltracker.com/help/articles/githubs_service_hook_for_tracker/ - */ - export function getPivotalStoryIDs(message: string): ReadonlyArray { -- const matches = message.match(/^\[(#\d+(,#\d+)*)\]\s.+/); -+ const matches = message.match(/\[(#\d+(,#\d+)*)\]\s.+/); - if(matches) { - return matches[1] - .split(",") diff --git a/patches/patches.md b/patches/patches.md index 86d924da6df..2f1166338a2 100644 --- a/patches/patches.md +++ b/patches/patches.md @@ -77,12 +77,6 @@ Updated on **29/08/2022** - This patch is going to fix a gradle issue that breaks the compile on android platform, due to gradle imcompatibility -### danger-plugin-digitalcitizenship+0.3.1 -Created on **06/08/2020** - -#### Reason: -- Recognizes the ids of pivotal stories even if they are not at the beginning of the line - ### react-native-push-notification+7.3.1 Created on **10/05/2021** diff --git a/scripts/changelog/add_pivotal_stories.js b/scripts/changelog/add_jira_stories.js similarity index 51% rename from scripts/changelog/add_pivotal_stories.js rename to scripts/changelog/add_jira_stories.js index a886b6bb01a..71025612d03 100644 --- a/scripts/changelog/add_pivotal_stories.js +++ b/scripts/changelog/add_jira_stories.js @@ -1,42 +1,12 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires const fs = require("fs-extra"); -const Pivotal = require("pivotaljs"); -const pivotal = new Pivotal(); const jiraTicketBaseUrl = "https://pagopa.atlassian.net/browse/"; -/** - * Skip the use of API to find the story url if the url is already retrieved (contains pivotaltracker.com) - * or the id length is < 9 (not a pivotal id) - * @param id - * @param url - * @return {*|boolean} - */ -function skipStoryCheck(id, url) { - return url.includes("pivotaltracker.com") || id.length < 9; -} - -async function replacePivotalUrl(match, storyId, url) { - const sameUrl = `[${storyId}](${url})`; - try { - // avoid to use the api if story is already linked to pivotal - if (skipStoryCheck(storyId, url)) { - return sameUrl; - } - // try to get the story with the specified id - const story = await getStory(storyId.substr(1)); - - // if the story doesn't exists (eg: story deleted) remove the markdown link - return story.url !== undefined - ? `[${storyId}](${story.url})` - : `${storyId}`; - } catch (err) { - return sameUrl; - } -} - async function addJiraUrl(match, ticketKeys) { return `[${ticketKeys .split(",") + // eslint-disable-next-line sonarjs/no-nested-template-literals .map(x => `[${x}](${new URL(x, jiraTicketBaseUrl).toString()})`)}]`; } @@ -47,7 +17,7 @@ async function replaceJiraStories(content) { } /** - * replace the changelog content by removing the repetition of "closes [#idPivotalorJiraStory](url)" + * replace the changelog content by removing the repetition of "closes [#idJiraStory](url)" * @param content * @return {Promise} */ @@ -61,8 +31,6 @@ async function addTasksUrls() { const rawChangelog = fs.readFileSync("CHANGELOG.md").toString("utf8"); const updatedChangelog = await [ - // Add pivotal stories url - replacePivotalStories, // Add jira ticket url replaceJiraStories, // clean closes @@ -75,14 +43,6 @@ async function addTasksUrls() { fs.writeFileSync("CHANGELOG.md", updatedChangelog); } -async function replacePivotalStories(content) { - // identify the pattern [#XXXXX](url) for markdown link - const pivotalTagRegex = /\[(#\d+)\]\(([a-zA-z/\d\-@:%._+~#=]+)\)/g; - - // check for all the matches if is a pivotal story and update the url - return await replaceAsync(content, pivotalTagRegex, replacePivotalUrl); -} - /** * A custom method created to allows the replace in a string using an async function * @param str @@ -94,29 +54,18 @@ async function replaceAsync(str, regex, asyncFn) { const promises = []; str.replace(regex, (match, ...args) => { const promise = asyncFn(match, ...args); + // eslint-disable-next-line functional/immutable-data promises.push(promise); }); const data = await Promise.all(promises); + // eslint-disable-next-line functional/immutable-data return str.replace(regex, () => data.shift()); } -/** - * Wrap the getStory callback with a promise - * @param storyId - * @return {Promise} - */ -getStory = storyId => - new Promise((resolve, reject) => { - pivotal.getStory(storyId, (err, story) => { - if (err) { - return reject(err); - } - resolve(story); - }); - }); - -// Execute the script to find the pivotal stories and jira ticket id in order to associate +// Execute the script to find jira ticket id in order to associate // the right url in the changelog addTasksUrls() + // eslint-disable-next-line no-console .then(() => console.log("done")) + // eslint-disable-next-line no-console .catch(ex => console.log(ex)); diff --git a/scripts/ts/common/ticket/jira/index.ts b/scripts/ts/common/jiraTicket/index.ts similarity index 66% rename from scripts/ts/common/ticket/jira/index.ts rename to scripts/ts/common/jiraTicket/index.ts index 87574219e9c..ffc7e493e99 100644 --- a/scripts/ts/common/ticket/jira/index.ts +++ b/scripts/ts/common/jiraTicket/index.ts @@ -3,7 +3,8 @@ import { flow, pipe } from "fp-ts/lib/function"; import * as TE from "fp-ts/lib/TaskEither"; import { Errors } from "io-ts"; import fetch from "node-fetch"; -import { RemoteJiraTicket } from "./types"; +import * as O from "fp-ts/lib/Option"; +import { JiraTicketRetrievalResults, RemoteJiraTicket } from "./types"; const jiraOrgBaseUrl = "https://pagopa.atlassian.net/rest/api/3/issue/"; export const jiraTicketBaseUrl = "https://pagopa.atlassian.net/browse/"; @@ -58,3 +59,39 @@ export const getJiraTickets = async ( jiraIds: ReadonlyArray ): Promise>> => await Promise.all(jiraIds.map(getJiraTicket)); + +const jiraRegex = /\[([A-Z0-9]+-\d+(,[A-Z0-9]+-\d+)*)]\s.+/; + +/** + * Extracts Jira ticket ids from the pr title (if any) + * @param title + */ +export const getJiraIdFromPrTitle = ( + title: string +): O.Option> => + pipe( + title.match(jiraRegex), + O.fromNullable, + O.map(a => a[1].split(",")) + ); + +/** + * Try to retrieve Jira tickets from pr title + * and transforms them into {@link GenericTicket} + * @param title + */ +export const getTicketsFromTitle = async ( + title: string +): Promise => { + const maybeJiraId = await pipe( + getJiraIdFromPrTitle(title), + O.map(getJiraTickets), + O.toUndefined + ); + + if (maybeJiraId) { + return maybeJiraId; + } else { + return [E.left(new Error("No Jira ticket found"))]; + } +}; diff --git a/scripts/ts/common/ticket/jira/types.ts b/scripts/ts/common/jiraTicket/types.ts similarity index 72% rename from scripts/ts/common/ticket/jira/types.ts rename to scripts/ts/common/jiraTicket/types.ts index d4d6aefb192..8a2654778d6 100644 --- a/scripts/ts/common/ticket/jira/types.ts +++ b/scripts/ts/common/jiraTicket/types.ts @@ -1,4 +1,6 @@ import * as t from "io-ts"; +import * as E from "fp-ts/lib/Either"; +import { Errors } from "io-ts"; const JiraIssueType = t.keyof({ Epic: null, @@ -12,18 +14,18 @@ const JiraIssueType = t.keyof({ export type JiraIssueType = t.TypeOf; -const IssueType = t.interface({ +const IssueType = t.type({ name: JiraIssueType, subtask: t.boolean }); -const Project = t.interface({ +const Project = t.type({ id: t.string, key: t.string, name: t.string }); -const FieldsR = t.interface({ +const FieldsR = t.type({ issuetype: IssueType, project: Project, labels: t.array(t.string), @@ -31,9 +33,9 @@ const FieldsR = t.interface({ }); const FieldsP = t.partial({ - parent: t.interface({ + parent: t.type({ key: t.string, - fields: t.interface({ + fields: t.type({ summary: t.string, issuetype: IssueType }) @@ -53,3 +55,11 @@ export const RemoteJiraTicket = t.interface({ }); export type RemoteJiraTicket = t.TypeOf; + +export type RemoteJiraTicketParent = Required< + t.TypeOf +>["parent"]; + +export type JiraTicketRetrievalResults = ReadonlyArray< + E.Either +>; diff --git a/scripts/ts/common/ticket/pivotal/types.d.ts b/scripts/ts/common/ticket/pivotal/types.d.ts deleted file mode 100644 index fdb6f5da873..00000000000 --- a/scripts/ts/common/ticket/pivotal/types.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -// TODO: create types for pivotaljs - -import { IUnitTag } from "@pagopa/ts-commons/lib/units"; - -export type PivotalStoryType = "feature" | "bug" | "chore" | "release"; -export type PivotalStoryCurrentState = - | "accepted" - | "delivered" - | "finished" - | "started" - | "rejected" - | "planned" - | "unstarted" - | "unscheduled"; - -export type PivotalLabel = { - id: number; - project_id: number; - kind: string; - name: string; - // TODO: should be date - created_at: string; - updated_at: string; -}; - -export type PivotalId = string & IUnitTag<"PivotalId">; - -export type PivotalStory = { - id: string; - story_type: PivotalStoryType; - created_at: string; - updated_at: string; - estimate: number; - name: string; - current_state: PivotalStoryCurrentState; - url: string; - project_id: number; - labels: ReadonlyArray; -}; diff --git a/scripts/ts/common/ticket/types.ts b/scripts/ts/common/ticket/types.ts deleted file mode 100644 index e980641bd76..00000000000 --- a/scripts/ts/common/ticket/types.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { jiraTicketBaseUrl } from "./jira"; -import { JiraIssueType, RemoteJiraTicket } from "./jira/types"; -import { PivotalStory, PivotalStoryType } from "./pivotal/types"; - -export type GenericTicketType = "feat" | "fix" | "chore" | "epic"; - -/** - * A generic representation of a ticket, platform independent - */ -export type GenericTicket = { - id: string; - // a prefix that should be used to represent the ticket id - idPrefix?: string; - title: string; - type: GenericTicketType; - projectId: string; - tags: ReadonlyArray; - // the url to reach the ticket - url: string; - // is a subtask linked with a father ticket? - parent?: GenericTicket; -}; - -/** - * From {@link JiraIssueType} to {@link GenericTicketType} - * @param jiraType - */ -const convertJiraTypeToGeneric = ( - jiraType: JiraIssueType -): GenericTicketType => { - switch (jiraType) { - case "Bug": - return "fix"; - case "Epic": - return "epic"; - case "Sub-task": - case "Sottotask": - case "Subtask": - return "chore"; - case "Story": - return "feat"; - case "Task": - return "chore"; - } -}; - -/** - * Extracts the Jira ticket type. If is a subtask, use the father ticket for the type - * @param jira - */ -const getTypeFromJira = (jira: RemoteJiraTicket) => { - // if this is a subtask with a parent, the type is the type of the parent - if (jira.fields.parent !== undefined && jira.fields.issuetype.subtask) { - return convertJiraTypeToGeneric(jira.fields.parent.fields.issuetype.name); - } - return convertJiraTypeToGeneric(jira.fields.issuetype.name); -}; - -/** - * From {@link RemoteJiraTicket} to {@link GenericTicket} - * @param jira - */ -export const fromJiraToGenericTicket = ( - jira: RemoteJiraTicket -): GenericTicket => ({ - id: jira.key, - title: jira.fields.summary, - type: getTypeFromJira(jira), - projectId: jira.fields.project.key, - tags: jira.fields.labels, - url: new URL(jira.key, jiraTicketBaseUrl).toString(), - parent: jira.fields.parent - ? fromJiraToGenericTicket({ - ...jira.fields.parent, - fields: { - ...jira.fields.parent.fields, - project: jira.fields.project, - labels: [] - } - }) - : undefined -}); - -/** - * From {@link PivotalStoryType} to {@link GenericTicketType} - * @param pivotalType - */ -const convertPivotalTypeToGeneric = ( - pivotalType: PivotalStoryType -): GenericTicketType => { - switch (pivotalType) { - case "bug": - return "fix"; - case "feature": - return "feat"; - case "chore": - return "chore"; - case "release": - return "feat"; - } -}; - -/** - * From {@link PivotalStory} to {@link GenericTicket} - * @param pivotalStory - */ -export const fromPivotalToGenericTicket = ( - pivotalStory: PivotalStory -): GenericTicket => ({ - id: pivotalStory.id, - idPrefix: "#", - title: pivotalStory.name, - type: convertPivotalTypeToGeneric(pivotalStory.story_type), - projectId: pivotalStory.project_id.toString(), - tags: pivotalStory.labels.map(x => x.name), - url: pivotalStory.url -}); diff --git a/scripts/ts/danger/__mocks__/genericTicket.ts b/scripts/ts/danger/__mocks__/genericTicket.ts new file mode 100644 index 00000000000..435ff4d096a --- /dev/null +++ b/scripts/ts/danger/__mocks__/genericTicket.ts @@ -0,0 +1,21 @@ +import { RemoteJiraTicket } from "../../common/jiraTicket/types"; + +export const getJiraTicketExample = ( + issueType: RemoteJiraTicket["fields"]["issuetype"]["name"], + projectKey: RemoteJiraTicket["fields"]["project"]["key"] = "IO" +): RemoteJiraTicket => ({ + key: "IO-123", + fields: { + issuetype: { + name: issueType, + subtask: false + }, + project: { + id: "123", + key: projectKey, + name: "IO" + }, + labels: [], + summary: "Test" + } +}); diff --git a/scripts/ts/danger/__tests__/updatePrTitle.test.ts b/scripts/ts/danger/__tests__/updatePrTitle.test.ts new file mode 100644 index 00000000000..b456566b0c3 --- /dev/null +++ b/scripts/ts/danger/__tests__/updatePrTitle.test.ts @@ -0,0 +1,118 @@ +import * as O from "fp-ts/lib/Option"; +import * as E from "fp-ts/lib/Either"; +import { pipe } from "fp-ts/lib/function"; +import { getJiraTicketExample } from "../__mocks__/genericTicket"; +import { + allStoriesSameType, + getChangelogPrefixByStories, + getChangelogScope, + getTicketChangelogScope +} from "../updatePrTitle"; +import { RemoteJiraTicket } from "../../common/jiraTicket/types"; + +describe("changelog", () => { + const storyTicket = getJiraTicketExample("Story"); + const subTaskTicket = getJiraTicketExample("Sub-task"); + const bugTicket = getJiraTicketExample("Bug"); + const storyCrossTicket = getJiraTicketExample("Story", "IOAPPX"); + const storyFciTicket = getJiraTicketExample("Story", "SFEQS"); + + describe("allStoriesSameType", () => { + it("should return true if all stories have the same type", () => { + const stories: Array = [storyTicket, storyTicket]; + const result = allStoriesSameType(stories); + expect(result).toBe(true); + }); + + it("should return false if stories have different types", () => { + const stories: Array = [storyTicket, subTaskTicket]; + const result = allStoriesSameType(stories); + expect(result).toBe(false); + }); + }); + + describe("getChangelogPrefixByStories", () => { + it("should return the common prefix if all stories have the same type", () => { + const stories: Array = [storyTicket, storyTicket]; + const result = getChangelogPrefixByStories(stories); + expect(O.isSome(result)).toBe(true); + expect(O.getOrElse(() => "")(result)).toBe("feat"); + }); + + it("should return feat if stories have different types but there's at least one feat", () => { + const stories: Array = [storyTicket, subTaskTicket]; + const result = getChangelogPrefixByStories(stories); + expect(O.isSome(result)).toBe(true); + expect(O.getOrElse(() => "")(result)).toBe("feat"); + }); + + it("should return fix if stories has a Bug ticket type", () => { + const stories: Array = [bugTicket]; + const result = getChangelogPrefixByStories(stories); + expect(O.isSome(result)).toBe(true); + expect(O.getOrElse(() => "")(result)).toBe("fix"); + }); + }); + + describe("getTicketChangelogScope", () => { + it("should return the changelog scope if the story belongs to a scope project", () => { + const result = getTicketChangelogScope(storyCrossTicket); + expect(O.isSome(result)).toBe(true); + expect(O.getOrElse(() => "")(result)).toBe("Cross"); + }); + + it("should return None if the story doesn't belong to a scope project", () => { + const result = getTicketChangelogScope(storyTicket); + expect(O.isNone(result)).toBe(true); + }); + }); + + describe("getChangelogScope", () => { + it("should return Right(None) if no scope is found", () => { + const stories: Array = [storyTicket, subTaskTicket]; + const result = getChangelogScope(stories); + pipe( + result, + E.fold( + () => + fail( + `getChangelogScope shouldn't return Left if no scope is found` + ), + scope => { + expect(O.isNone(scope)).toBe(true); + } + ) + ); + }); + + it("should return Right(Some(scope)) if one of the stories have a scope or all the stories that have scope have the same scope", () => { + const stories: Array = [ + storyCrossTicket, + storyCrossTicket + ]; + const result = getChangelogScope(stories); + pipe( + result, + E.fold( + () => + fail( + `getChangelogScope shouldn't return Left if one of the stories have a scope or all the stories that have scope have the same scope` + ), + scope => { + expect(O.isSome(scope)).toBe(true); + expect(O.getOrElse(() => "")(scope)).toBe("Cross"); + } + ) + ); + }); + + it("should return Left(errors) if two stories have different scope or one of the story have different scope", () => { + const stories: Array = [ + storyFciTicket, + storyCrossTicket + ]; + const result = getChangelogScope(stories); + expect(E.isLeft(result)).toBe(true); + }); + }); +}); diff --git a/scripts/ts/danger/commentPrWithTicketsInfo.ts b/scripts/ts/danger/commentPr.ts similarity index 60% rename from scripts/ts/danger/commentPrWithTicketsInfo.ts rename to scripts/ts/danger/commentPr.ts index 31d0b325974..f4af572230f 100644 --- a/scripts/ts/danger/commentPrWithTicketsInfo.ts +++ b/scripts/ts/danger/commentPr.ts @@ -2,33 +2,35 @@ import { DangerDSLType } from "danger/distribution/dsl/DangerDSL"; import * as E from "fp-ts/lib/Either"; import { Errors } from "io-ts"; import { readableReport } from "@pagopa/ts-commons/lib/reporters"; -import { GenericTicket, GenericTicketType } from "../common/ticket/types"; -import { GenericTicketRetrievalResults } from "./utils/titleParser"; +import { + JiraTicketRetrievalResults, + JiraIssueType, + RemoteJiraTicket, + RemoteJiraTicketParent +} from "../common/jiraTicket/types"; +import { jiraTicketBaseUrl } from "../common/jiraTicket"; declare const danger: DangerDSLType; export declare function warn(message: string): void; export declare function markdown(message: string): void; -const StoryEmoji: Record = { - feat: "🌟", - fix: "🐞", - chore: "⚙️", - epic: "⚡" +const StoryEmoji: Record = { + Epic: "⚡", + Story: "🌟", + Task: "⚙️", + Subtask: "⚙️", + "Sub-task": "⚙️", + Sottotask: "⚙️", + Bug: "🐞" }; /** - * Display how should be used the Jira / Pivotal id in the pr title + * Display how should be used the Jira id in the pr title */ const warningNoTicket = () => { - warn( - "Please include a Pivotal story or Jira ticket at the beginning of the PR title" - ); - markdown(` - Example of PR titles that include pivotal stories: - * single story: \`[#123456] my PR title\` - * multiple stories: \`[#123456,#123457,#123458] my PR title\` - + warn("Please include a Jira ticket at the beginning of the PR title"); + markdown(` Example of PR titles that include Jira tickets: * single story: \`[PROJID-123] my PR title\` * multiple stories: \`[PROJID-1,PROJID-2,PROJID-3] my PR title\` @@ -38,16 +40,17 @@ const warningNoTicket = () => { * Comments with the ticket type, id and title * @param ticket */ -const renderTicket = (ticket: GenericTicket) => - `${StoryEmoji[ticket.type]} [${ticket.idPrefix ? ticket.idPrefix : ""}${ - ticket.id - }](${ticket.url}): ${ticket.title}`; +const renderTicket = (ticket: RemoteJiraTicket | RemoteJiraTicketParent) => { + const ticketType = StoryEmoji[ticket.fields.issuetype.name]; + const ticketUrl = new URL(ticket.key, jiraTicketBaseUrl).toString(); + return `${ticketType} [${ticket.key}](${ticketUrl}): ${ticket.fields.summary}`; +}; -const renderTickets = (ticketList: ReadonlyArray) => { +const renderTickets = (ticketList: ReadonlyArray) => { const ticketListToString = ticketList .map(s => { - const subtask = s.parent - ? ` \n _subtask of_\n * ${renderTicket(s.parent)}` + const subtask = s.fields.parent + ? ` \n _subtask of_\n * ${renderTicket(s.fields.parent)}` : ""; return ` * ${renderTicket(s)}${subtask}`; }) @@ -76,7 +79,7 @@ const renderFailure = (errors: ReadonlyArray) => { * @param foundTicket */ export const commentPrWithTicketsInfo = ( - foundTicket: GenericTicketRetrievalResults + foundTicket: JiraTicketRetrievalResults ) => { if (foundTicket.length === 0) { warningNoTicket(); diff --git a/scripts/ts/danger/updatePrTitle.tsx b/scripts/ts/danger/updatePrTitle.tsx new file mode 100644 index 00000000000..4dc968dada8 --- /dev/null +++ b/scripts/ts/danger/updatePrTitle.tsx @@ -0,0 +1,208 @@ +import { DangerDSLType } from "danger/distribution/dsl/DangerDSL"; +import * as E from "fp-ts/lib/Either"; +import { flow, pipe } from "fp-ts/lib/function"; +import * as O from "fp-ts/lib/Option"; +import { + JiraTicketRetrievalResults, + JiraIssueType, + RemoteJiraTicket +} from "../common/jiraTicket/types"; + +declare const danger: DangerDSLType; +export declare function warn(message: string): void; + +const multipleTypesWarning = + "Multiple stories with different types are associated with this Pull request.\n" + + "Only one tag will be added, following the order: `feature > bug > chore`"; + +const atLeastOneLeftTicket = + "An error occurred during the retrieval of a ticket," + + " the title of the pull request is updated taking into account only the recovered tickets "; + +const storyTag = new Map([ + ["Epic", "feat"], + ["Story", "feat"], + ["Bug", "fix"], + ["Sub-task", "chore"], + ["Sottotask", "chore"], + ["Subtask", "chore"], + ["Task", "chore"] +]); + +const storyOrder = new Map([ + ["Epic", 3], + ["Story", 2], + ["Bug", 1], + ["Sub-task", 0], + ["Sottotask", 0], + ["Subtask", 0], + ["Task", 0] +]); + +// a list of project ids associated with a specific scope +const projectToScope = new Map([ + ["IOAPPX", "Cross"], + ["SFEQS", "Firma con IO"], + ["IODPAY", "IDPay"] +]); + +/** + * Calculate the Changelog prefix for the provided stories. + * @param stories + */ +export const getChangelogPrefixByStories = ( + stories: ReadonlyArray +): O.Option => { + // In case of multiple stories, only one tag can be added, following the order feature > bug > chore + const storyType = stories.reduce>((acc, val) => { + const currentStoryOrder = O.fromNullable( + storyOrder.get(val.fields.issuetype.name) + ); + const prevStoryOrder = pipe( + acc, + O.chain(v => O.fromNullable(storyOrder.get(v))) + ); + + if (O.isSome(currentStoryOrder) && O.isSome(prevStoryOrder)) { + return currentStoryOrder.value > prevStoryOrder.value + ? O.some(val.fields.issuetype.name) + : acc; + } else if (O.isSome(currentStoryOrder)) { + return O.some(val.fields.issuetype.name); + } + return acc; + }, O.none); + + return pipe( + storyType, + O.chain(st => O.fromNullable(storyTag.get(st))) + ); +}; + +/** + * Append the changelog tag and scope to the pull request title, based on the story found + */ +export const updatePrTitleForChangelog = async ( + tickets: JiraTicketRetrievalResults +) => { + if (tickets.some(E.isLeft)) { + warn(atLeastOneLeftTicket); + } + + const foundTicket = tickets.filter(E.isRight).map(x => x.right); + + if (!allStoriesSameType(foundTicket)) { + warn(multipleTypesWarning); + } + const maybePrTag = getChangelogPrefixByStories(foundTicket); + const eitherScope = getChangelogScope(foundTicket); + + if (E.isLeft(eitherScope)) { + eitherScope.left.map(err => warn(err.message)); + } + const scope = pipe( + eitherScope, + E.map( + flow( + O.map(s => `(${s})`), + O.getOrElse(() => "") + ) + ), + E.getOrElseW(() => "") + ); + + const cleanChangelogRegex = + /^(fix(\(.+\))?!?: |feat(\(.+\))?!?: |chore(\(.+\))?!?: )?(.*)$/; + const title = pipe( + danger.github.pr.title.match(cleanChangelogRegex), + O.fromNullable, + O.map(matches => matches.pop() || danger.github.pr.title), + O.getOrElse(() => danger.github.pr.title) + ); + + const labelScope = scope.replace("(", "").replace(")", ""); + await danger.github.utils.createOrAddLabel({ + name: labelScope, + // The color is not used and can be customized from the "label" tab in the github page + color: "#FFFFFF", + description: labelScope + }); + + // Ensure the first char after the ticket id is uppercase + // [JIRA-12] pr title -> [JIRA-12] Pr title + const titleSplitter = new RegExp(/(\[.*]\s*)(.+)/g); + const splittingResults = titleSplitter.exec(title); + const upperCaseTitle = + splittingResults && splittingResults.length === 3 + ? `${splittingResults[1]}${ + splittingResults[2].charAt(0).toUpperCase() + + splittingResults[2].slice(1) + }` + : title; + + O.map(tag => + danger.github.api.pulls.update({ + owner: danger.github.thisPR.owner, + repo: danger.github.thisPR.repo, + pull_number: danger.github.thisPR.number, + title: `${tag}${scope}: ${upperCaseTitle}` + }) + )(maybePrTag); +}; + +/** + * Return true if all the stories have the same `story_type` + * @param stories + */ +export const allStoriesSameType = ( + stories: ReadonlyArray +): boolean => + stories.every( + (val, _, arr) => val.fields.issuetype === arr[0].fields.issuetype + ); + +/** + * Calculate the changelog scope for the story + * Return: + * - O.None if the story doesn't belong to a scope project + * - O.Some>` if the story belongs to a scope project + * @param story + */ +export const getTicketChangelogScope = ( + story: RemoteJiraTicket +): O.Option => + O.fromNullable(projectToScope.get(story.fields.project.key)); + +/** + * Calculate the Changelog scope for the stories. + * Return: + * - `Right` if no scope is found + * - `Right>` if one of the stories have a scope or all the stories that have scope have the same scope + * - `Left>` if two stories have different scope or one of the story have different scope + * @param stories + */ + +export const getChangelogScope = ( + stories: ReadonlyArray +): E.Either, O.Option> => { + const eitherChangelogScopes = stories.map(getTicketChangelogScope); + + const scopesList = eitherChangelogScopes + .filter((maybeScope): maybeScope is O.Some => O.isSome(maybeScope)) + .map(scope => scope.value); + + if (scopesList.length === 0) { + return E.right(O.none); + } + + return scopesList.every((scope, _, arr) => scope === arr[0]) + ? E.right(O.some(scopesList[0])) + : E.left([ + new Error( + `Different scopes were found on the stories related to the pull request: [${scopesList.join( + "," + )}].\n + It is not possible to assign a single scope to this pull request!` + ) + ]); +}; diff --git a/scripts/ts/danger/updatePrTitleForChangelog.tsx b/scripts/ts/danger/updatePrTitleForChangelog.tsx deleted file mode 100644 index eae6f61b5c0..00000000000 --- a/scripts/ts/danger/updatePrTitleForChangelog.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { DangerDSLType } from "danger/distribution/dsl/DangerDSL"; -import * as E from "fp-ts/lib/Either"; -import { flow, pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { - allStoriesSameType, - getChangelogPrefixByStories, - getChangelogScope -} from "./utils/changelog"; -import { GenericTicketRetrievalResults } from "./utils/titleParser"; - -declare const danger: DangerDSLType; -export declare function warn(message: string): void; - -const multipleTypesWarning = - "Multiple stories with different types are associated with this Pull request.\n" + - "Only one tag will be added, following the order: `feature > bug > chore`"; - -const atLeastOneLeftTicket = - "An error occurred during the retrieval of a ticket," + - " the title of the pull request is updated taking into account only the recovered tickets "; - -/** - * Append the changelog tag and scope to the pull request title, based on the story found - */ -export const updatePrTitleForChangelog = async ( - tickets: GenericTicketRetrievalResults -) => { - if (tickets.some(E.isLeft)) { - warn(atLeastOneLeftTicket); - } - - const foundTicket = tickets.filter(E.isRight).map(x => x.right); - - if (!allStoriesSameType(foundTicket)) { - warn(multipleTypesWarning); - } - const maybePrTag = getChangelogPrefixByStories(foundTicket); - const eitherScope = getChangelogScope(foundTicket); - - if (E.isLeft(eitherScope)) { - eitherScope.left.map(err => warn(err.message)); - } - const scope = pipe( - eitherScope, - E.map( - flow( - O.map(s => `(${s})`), - O.getOrElse(() => "") - ) - ), - E.getOrElseW(() => "") - ); - - const cleanChangelogRegex = - /^(fix(\(.+\))?!?: |feat(\(.+\))?!?: |chore(\(.+\))?!?: )?(.*)$/; - const title = pipe( - danger.github.pr.title.match(cleanChangelogRegex), - O.fromNullable, - O.map(matches => matches.pop() || danger.github.pr.title), - O.getOrElse(() => danger.github.pr.title) - ); - - const labelScope = scope.replace("(", "").replace(")", ""); - await danger.github.utils.createOrAddLabel({ - name: labelScope, - // The color is not used and can be customized from the "label" tab in the github page - color: "#FFFFFF", - description: labelScope - }); - - // Ensure the first char after the ticket id is uppercase - // [JIRA-12] pr title -> [JIRA-12] Pr title - const titleSplitter = new RegExp(/(\[.*]\s*)(.+)/g); - const splittingResults = titleSplitter.exec(title); - const upperCaseTitle = - splittingResults && splittingResults.length === 3 - ? `${splittingResults[1]}${ - splittingResults[2].charAt(0).toUpperCase() + - splittingResults[2].slice(1) - }` - : title; - - O.map(tag => - danger.github.api.pulls.update({ - owner: danger.github.thisPR.owner, - repo: danger.github.thisPR.repo, - pull_number: danger.github.thisPR.number, - title: `${tag}${scope}: ${upperCaseTitle}` - }) - )(maybePrTag); -}; diff --git a/scripts/ts/danger/utils/__mocks__/storyMock.ts b/scripts/ts/danger/utils/__mocks__/storyMock.ts deleted file mode 100644 index f312ef135a5..00000000000 --- a/scripts/ts/danger/utils/__mocks__/storyMock.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { - PivotalLabel, - PivotalStory -} from "../../../common/ticket/pivotal/types"; - -export const baseStory: PivotalStory = { - id: "123", - story_type: "bug", - created_at: "date", - updated_at: "date", - estimate: 3, - name: "Come CIT voglio poter utilizzare l'app", - current_state: "started", - url: "url", - project_id: 132, - labels: [] -}; - -export const baseLabel: PivotalLabel = { - id: 5, - project_id: 21, - kind: "label", - name: "generic-label", - created_at: "date", - updated_at: "date" -}; - -export const scopeLabeliOS: PivotalLabel = { - ...baseLabel, - name: "changelog-scope:ios" -}; - -export const scopeLabelAndroid: PivotalLabel = { - ...baseLabel, - name: "changelog-scope:android" -}; - -export const scopeLabelEpicBpd: PivotalLabel = { - ...baseLabel, - name: "epic-bpd" -}; - -export const scopeLabelNotAllowed: PivotalLabel = { - ...baseLabel, - name: "changelog-scope:not-allowed" -}; - -export const bonusVacanzeStory: PivotalStory = { - ...baseStory, - project_id: 2449547 -}; - -export const baseStoryWithGenericLabel: PivotalStory = { - ...baseStory, - labels: [baseLabel] -}; - -export const bonusVacanzeStoryWithScopeLabel: PivotalStory = { - ...bonusVacanzeStory, - labels: [scopeLabeliOS] -}; - -export const singleAndroidLabelStory: PivotalStory = { - ...baseStory, - labels: [scopeLabelAndroid] -}; - -export const singleEpicBpdStory: PivotalStory = { - ...baseStory, - labels: [scopeLabelEpicBpd] -}; - -export const androidLabelAndOtherStory: PivotalStory = { - ...baseStory, - labels: [scopeLabelAndroid, baseLabel] -}; - -export const clashScopeLabelStory: PivotalStory = { - ...baseStory, - labels: [scopeLabelAndroid, scopeLabeliOS] -}; - -export const scopeLabelNotAllowedStory: PivotalStory = { - ...baseStory, - labels: [scopeLabelNotAllowed] -}; diff --git a/scripts/ts/danger/utils/__tests__/changelog.test.ts b/scripts/ts/danger/utils/__tests__/changelog.test.ts deleted file mode 100644 index 360b56b36aa..00000000000 --- a/scripts/ts/danger/utils/__tests__/changelog.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import * as E from "fp-ts/lib/Either"; -import { - androidLabelAndOtherStory, - baseStory, - baseStoryWithGenericLabel, - bonusVacanzeStory, - bonusVacanzeStoryWithScopeLabel, - clashScopeLabelStory, - scopeLabelNotAllowedStory, - singleAndroidLabelStory, - singleEpicBpdStory -} from "../__mocks__/storyMock"; -import { getChangelogScope, getStoryChangelogScope } from "../changelog"; -import { fromPivotalToGenericTicket } from "../../../common/ticket/types"; - -describe("Test pivotal Utility", () => { - it("getStoryChangelogScope on a story without labels should return Right O.none", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(baseStory) - ); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope)) { - expect(eitherScope.right).toBe(O.none); - } - }); - it("getStoryChangelogScope on a story with a normal label (not scope tag) should return Right,O.none", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(baseStoryWithGenericLabel) - ); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope)) { - expect(eitherScope.right).toBe(O.none); - } - }); - it("getStoryChangelogScope on a story without label but belonging to a project that assigns a scope should return Right,string", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(bonusVacanzeStory) - ); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope) && O.isSome(eitherScope.right)) { - expect(eitherScope.right.value).toBe("Bonus Vacanze"); - return; - } - fail( - "Condition E.isRight(eitherScope) && eitherScope.value.O.isSome() not satisfied" - ); - }); - it("getStoryChangelogScope on a story with scope label should return Right,string", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(singleAndroidLabelStory) - ); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope) && O.isSome(eitherScope.right)) { - expect(eitherScope.right.value).toBe("Android"); - return; - } - fail( - "Condition E.isRight(eitherScope) && eitherScope.value.O.isSome() not satisfied" - ); - }); - it("getStoryChangelogScope on a story with epic label should return Right,string", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(singleEpicBpdStory) - ); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope) && O.isSome(eitherScope.right)) { - expect(eitherScope.right.value).toBe("Bonus Pagamenti Digitali"); - return; - } - fail( - "Condition E.isRight(eitherScope) && eitherScope.value.O.isSome() not satisfied" - ); - }); - it("getStoryChangelogScope on a story with scope label and other labels should return Right,string", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(androidLabelAndOtherStory) - ); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope) && O.isSome(eitherScope.right)) { - expect(eitherScope.right.value).toBe("Android"); - return; - } - fail( - "Condition E.isRight(eitherScope) && eitherScope.value.O.isSome() not satisfied" - ); - }); - it("getStoryChangelogScope on a story with different scope labels should return Left,Error", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(clashScopeLabelStory) - ); - expect(E.isLeft(eitherScope)).toBeTruthy(); - }); - it("getStoryChangelogScope on a story with scope label not allowed should return Right,O.none", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(scopeLabelNotAllowedStory) - ); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope)) { - expect(eitherScope.right).toBe(O.none); - } - }); - it("getStoryChangelogScope on a story with a scope labels and belonging to a project that assigns a scope should return Left,Error", () => { - const eitherScope = getStoryChangelogScope( - fromPivotalToGenericTicket(bonusVacanzeStoryWithScopeLabel) - ); - expect(E.isLeft(eitherScope)).toBeTruthy(); - }); - - it("getChangelogScope on an empty array should return O.none", () => { - const eitherScope = getChangelogScope([]); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope)) { - expect(eitherScope.right).toBe(O.none); - } - }); - - it("getChangelogScope with a single story without scope label should return Right, O.none", () => { - const eitherScope = getChangelogScope([ - fromPivotalToGenericTicket(baseStoryWithGenericLabel) - ]); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope)) { - expect(eitherScope.right).toBe(O.none); - } - }); - it("getChangelogScope with a single story with scope label should return Right, some", () => { - const eitherScope = getChangelogScope([ - fromPivotalToGenericTicket(singleAndroidLabelStory) - ]); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope) && O.isSome(eitherScope.right)) { - expect(eitherScope.right.value).toBe("Android"); - return; - } - fail( - "Condition E.isRight(eitherScope) && eitherScope.value.O.isSome() not satisfied" - ); - }); - it("getChangelogScope with a multiple stories with the same scope label should return Right, some", () => { - const eitherScope = getChangelogScope([ - fromPivotalToGenericTicket(singleAndroidLabelStory), - fromPivotalToGenericTicket(singleAndroidLabelStory), - fromPivotalToGenericTicket(singleAndroidLabelStory) - ]); - expect(E.isRight(eitherScope)).toBeTruthy(); - if (E.isRight(eitherScope) && O.isSome(eitherScope.right)) { - expect(eitherScope.right.value).toBe("Android"); - return; - } - fail( - "Condition E.isRight(eitherScope) && eitherScope.value.O.isSome() not satisfied" - ); - }); - it("getChangelogScope with stories with different scopes should return Left, error[] with 1 error", () => { - const eitherScope = getChangelogScope([ - fromPivotalToGenericTicket(singleAndroidLabelStory), - fromPivotalToGenericTicket(bonusVacanzeStory) - ]); - expect(E.isLeft(eitherScope)).toBeTruthy(); - if (E.isLeft(eitherScope)) { - expect(eitherScope.left.length).toBe(1); - } - }); - it( - "getChangelogScope with stories with different scopes (foreach story) should return Left, " + - "error[] with 2 error", - () => { - const eitherScope = getChangelogScope([ - fromPivotalToGenericTicket(clashScopeLabelStory), - fromPivotalToGenericTicket(bonusVacanzeStoryWithScopeLabel) - ]); - expect(E.isLeft(eitherScope)).toBeTruthy(); - if (E.isLeft(eitherScope)) { - expect(eitherScope.left.length).toBe(2); - } - } - ); -}); diff --git a/scripts/ts/danger/utils/changelog.ts b/scripts/ts/danger/utils/changelog.ts deleted file mode 100644 index 40b370a298b..00000000000 --- a/scripts/ts/danger/utils/changelog.ts +++ /dev/null @@ -1,217 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -// The script need to be executed by the danger bot that doesn't support the optional chaining operator -import * as E from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { GenericTicket, GenericTicketType } from "../../common/ticket/types"; - -const storyTag = new Map([ - ["feat", "feat"], - ["fix", "fix"], - ["chore", "chore"], - ["epic", "feat"] -]); - -const storyOrder = new Map([ - ["epic", 3], - ["feat", 2], - ["fix", 1], - ["chore", 0] -]); - -const allowedScope = new Map([ - ["android", "Android"], - ["ios", "iOS"], - ["bonus_vacanze", "Bonus Vacanze"], - ["messages", "Messages"], - ["payments", "Payments"], - ["services", "Services"], - ["profile", "Profile"], - ["privacy", "Privacy"], - ["security", "Security"], - ["accessibility", "Accessibility"], - ["bpd", "Bonus Pagamenti Digitali"], - ["cgn", "Carta Giovani Nazionale"], - ["fims", "Federated Identity Management System"], - ["myportal", "MyPortal"] -]); - -// a list of project ids associated with a specific scope -const projectToScope = new Map([ - ["2449547", "Bonus Vacanze"], - ["2463683", "My Portal"], - ["2477137", "Bonus Pagamenti Digitali"], - ["IAC", "Bonus Pagamenti Digitali"], - ["IOACGN", "Carta Giovani Nazionale"], - ["IASV", "Sicilia Vola"], - ["IAGP", "EU Covid Certificate"], - ["IARS", "Redesign Servizi"], - ["ASZ", "Zendesk"], - ["IAMVL", "Piattaforma Notifiche"], - ["IAFIMS", "Federated Identity Management System"], - ["AP", "Carta della cultura"], - ["SFEQS", "Firma con IO"], - ["IODPAY", "IDPay"] -]); - -const cleanChangelogRegex = - /^(fix(\(.+\))?!?: |feat(\(.+\))?!?: |chore(\(.+\))?!?: )?(.*)$/; - -// pattern used to recognize a scope label -const regex = /(changelog-scope:|epic-)(.*)/m; - -/** - * Clean the title from previous changelog prefix to update in case of changes - * @param title - */ -export const getRawTitle = (title: string): string => { - // clean the title from existing tags (multiple commit on the same branch) - const rawTitle = title.match(cleanChangelogRegex)!.pop(); - return rawTitle !== undefined ? rawTitle : title; -}; - -/** - * Return true if all the stories have the same `story_type` - * @param stories - */ -export const allStoriesSameType = ( - stories: ReadonlyArray -): boolean => stories.every((val, _, arr) => val.type === arr[0].type); - -/** - * Calculate the Changelog prefix for the provided stories. - * @param stories - */ -export const getChangelogPrefixByStories = ( - stories: ReadonlyArray -): O.Option => { - // In case of multiple stories, only one tag can be added, following the order feature > bug > chore - const storyType = stories.reduce>((acc, val) => { - const currentStoryOrder = O.fromNullable(storyOrder.get(val.type)); - const prevStoryOrder = pipe( - acc, - O.chain(v => O.fromNullable(storyOrder.get(v))) - ); - - if (O.isSome(currentStoryOrder) && O.isSome(prevStoryOrder)) { - return currentStoryOrder.value > prevStoryOrder.value - ? O.some(val.type) - : acc; - } else if (O.isSome(currentStoryOrder)) { - return O.some(val.type); - } - return acc; - }, O.none); - - return pipe( - storyType, - O.chain(st => O.fromNullable(storyTag.get(st))) - ); -}; - -/** - * Calculate the changelog scope for the story - * Return: - * - `Right` if no labels with scope are associated with the story and the story doesn't belong to a scope project. - * - `Right>` if the story have a scope label or the story belong to a scope project - * - `Left` if the story have multiple different scope label or the story have a scope label and belong to a scope project. - * @param story - */ -export const getStoryChangelogScope = ( - story: GenericTicket -): E.Either> => { - // try to retrieve the project scope (if any) - const maybeProjectScope = O.fromNullable(projectToScope.get(story.projectId)); - // search for scope labels associated with the story - const maybeChangelogScopeTag = story.tags - .filter(l => l.match(regex)) - .map(l => l.match(regex)!.pop()) - .filter(tag => tag && allowedScope.has(tag)); - - // multiple scope labels found on the story - if (maybeChangelogScopeTag.length > 1) { - return E.left( - new Error( - `Multiple labels match the expression \`${regex}\` for the story [#${story.id}].\n - It is not possible to assign a single scope to this pull request!` - ) - ); - } - // the story matches a project scope and also have scope label - if (O.isSome(maybeProjectScope) && maybeChangelogScopeTag.length >= 1) { - return E.left( - new Error( - `The story [#${story.id}] have the project_id ${story.projectId} associated with the scope ${maybeProjectScope.value} but also have labels matching the expression \`${regex}\`.\n - It is not possible to assign a single scope to this pull request!` - ) - ); - } - if (O.isSome(maybeProjectScope)) { - return E.right(maybeProjectScope); - } - if ( - maybeChangelogScopeTag.length === 1 && - maybeChangelogScopeTag[0] !== undefined - ) { - // check if is allowed - const scopeDisplayName = allowedScope.get(maybeChangelogScopeTag[0]); - return scopeDisplayName !== undefined - ? E.right(O.some(scopeDisplayName)) - : E.left( - new Error( - `The scope ${ - maybeChangelogScopeTag[0] - } is not present in the allowed scopes: ${Array.from( - allowedScope.keys() - ).join(",")}` - ) - ); - } - // neither project scope nor scope label found - return E.right(O.none); -}; - -/** - * Calculate the Changelog scope for the stories. - * Return: - * - `Right` if no scope is found - * - `Right>` if one of the stories have a scope or all the stories that have scope have the same scope - * - `Left>` if two stories have different scope or one of the story have different scope - * @param stories - */ -export const getChangelogScope = ( - stories: ReadonlyArray -): E.Either, O.Option> => { - const eitherChangelogScopes = stories.map(getStoryChangelogScope); - - // if there is some error, forward the errors - if (eitherChangelogScopes.some(E.isLeft)) { - return E.left( - eitherChangelogScopes.reduce>( - (acc, val) => (E.isLeft(val) ? [...acc, val.left] : acc), - [] - ) - ); - } - const scopesList = eitherChangelogScopes - .filter( - (maybeScope): maybeScope is E.Right> => - E.isRight(maybeScope) && O.isSome(maybeScope.right) - ) - .map(scope => scope.right.value); - - if (scopesList.length === 0) { - return E.right(O.none); - } - - return scopesList.every((scope, _, arr) => scope === arr[0]) - ? E.right(O.some(scopesList[0])) - : E.left([ - new Error( - `Different scopes were found on the stories related to the pull request: [${scopesList.join( - "," - )}].\n - It is not possible to assign a single scope to this pull request!` - ) - ]); -}; diff --git a/scripts/ts/danger/utils/titleParser.tsx b/scripts/ts/danger/utils/titleParser.tsx deleted file mode 100644 index a4cc538792e..00000000000 --- a/scripts/ts/danger/utils/titleParser.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { - getPivotalStories, - getPivotalStoryIDs -} from "danger-plugin-digitalcitizenship/dist/utils"; -import * as E from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { Errors } from "io-ts"; -import { getJiraTickets } from "../../common/ticket/jira"; -import { - fromJiraToGenericTicket, - fromPivotalToGenericTicket, - GenericTicket -} from "../../common/ticket/types"; - -const jiraRegex = /\[([A-Z0-9]+-\d+(,[A-Z0-9]+-\d+)*)]\s.+/; - -export type GenericTicketRetrievalResults = ReadonlyArray< - E.Either ->; - -/** - * Extracts Jira ticket ids from the pr title (if any) - * @param title - */ -export const getJiraIdFromPrTitle = ( - title: string -): O.Option> => - pipe( - title.match(jiraRegex), - O.fromNullable, - O.map(a => a[1].split(",")) - ); - -/** - * Try to retrieve Jira tickets (or Pivotal stories as fallback) from pr title - * and transforms them into {@link GenericTicket} - * ⚠️ Mixed Jira and Pivotal id in the pr title are not supported ⚠️ - * @param title - */ -export const getTicketsFromTitle = async ( - title: string -): Promise => { - const maybeJiraId = await pipe( - getJiraIdFromPrTitle(title), - O.map(getJiraTickets), - O.toUndefined - ); - - if (maybeJiraId) { - return maybeJiraId.map(E.map(fromJiraToGenericTicket)); - } - - const maybePivotalId = await getPivotalStories(getPivotalStoryIDs(title)); - return maybePivotalId - ? pipe( - maybePivotalId - .filter(s => s.story_type !== undefined) - .map(fromPivotalToGenericTicket) - .map(E.right) - ) - : [E.left(new Error("No Pivotal stories found"))]; -}; diff --git a/yarn.lock b/yarn.lock index 3a737a2cc91..e7d8801e656 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4452,7 +4452,7 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.10.0, ajv@^6.12.3: +ajv@^6.10.0: version "6.12.3" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706" integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA== @@ -4766,18 +4766,6 @@ asn1.js@^5.2.0: minimalistic-assert "^1.0.0" safer-buffer "^2.1.0" -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -4887,16 +4875,6 @@ auto-changelog@^2.4.0: parse-github-url "^1.0.2" semver "^7.3.5" -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.8.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" - integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== - babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -5557,13 +5535,6 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - before-after-hook@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" @@ -6017,11 +5988,6 @@ capture-exit@^2.0.0: dependencies: rsvp "^4.8.4" -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - catharsis@^0.8.11: version "0.8.11" resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.8.11.tgz#d0eb3d2b82b7da7a3ce2efb1a7b00becc6643468" @@ -6382,7 +6348,7 @@ colors@1.4.0, colors@^1.0.3, colors@^1.1.2: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -6776,7 +6742,7 @@ core-js@^2.6.5: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== -core-util-is@1.0.2, core-util-is@~1.0.0: +core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= @@ -7020,25 +6986,6 @@ cycle@^1.0.3: resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI= -danger-plugin-digitalcitizenship@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/danger-plugin-digitalcitizenship/-/danger-plugin-digitalcitizenship-0.3.1.tgz#ed941ee5905788bb9f1f61f171029d76c985b995" - integrity sha512-AxaWGdLzm+GN7Q385ICCTxgj15JVIiFnpw03v1FcsCmDF1trnFhZfJzjESAJVi510p0C+bchJe9yX4SdsiUPXA== - dependencies: - danger-plugin-yarn "^1.2.1" - pivotaljs "^1.0.3" - -danger-plugin-yarn@^1.2.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/danger-plugin-yarn/-/danger-plugin-yarn-1.3.2.tgz#c1fc2b9ad24477775fd828473a0a33c62bd9a9b5" - integrity sha512-g+7guMig986giNyUxM15sFH32HKBGzHcKHMrAojvGOU9lpB53KwxX5yrYnokRlfW0aSaAwm9GCInXTQc9MoW3w== - dependencies: - date-fns "^1.28.5" - lodash.flatten "^4.4.0" - lodash.includes "^4.3.0" - node-fetch "^1.7.1" - semver "^5.4.1" - danger@^10.3.0: version "10.3.0" resolved "https://registry.yarnpkg.com/danger/-/danger-10.3.0.tgz#9d104a030998ac3491f05019e13b76e877111fad" @@ -7088,13 +7035,6 @@ dargs@^4.0.1: dependencies: number-is-nan "^1.0.0" -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - data-uri-to-buffer@3: version "3.0.1" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" @@ -7114,7 +7054,7 @@ date-and-time@0.9.0: resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-0.9.0.tgz#1524579e56dc07675c640b41735a7665c0659240" integrity sha512-4JybB6PbR+EebpFx/KyR5Ybl+TcdXMLIJkyYsCx3P4M4CWGMuDyFF19yh6TyasMAIF5lrsgIxiSHBXh2FFc7Fg== -date-fns@^1.28.5, date-fns@^1.29.0: +date-fns@^1.29.0: version "1.30.1" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== @@ -7569,14 +7509,6 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - ecdsa-sig-formatter@1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" @@ -8323,7 +8255,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.2: +extend@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -8342,16 +8274,6 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - fast-base64-decode@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz#b434a0dd7d92b12b43f26819300d2dafb83ee418" @@ -8649,11 +8571,6 @@ for-own@^0.1.3: dependencies: for-in "^1.0.1" -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - form-data@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" @@ -8672,15 +8589,6 @@ form-data@^3.0.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" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -8970,13 +8878,6 @@ getenv@0.7.0: resolved "https://registry.yarnpkg.com/getenv/-/getenv-0.7.0.tgz#39b91838707e2086fd1cf6ef8777d1c93e14649e" integrity sha1-ObkYOHB+IIb9HPbvh3fRyT4UZJ4= -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - git-config-path@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/git-config-path/-/git-config-path-1.0.1.tgz#6d33f7ed63db0d0e118131503bab3aca47d54664" @@ -9177,19 +9078,6 @@ handlebars@^4.7.6, handlebars@^4.7.7: optionalDependencies: uglify-js "^3.1.4" -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" @@ -9538,15 +9426,6 @@ http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - http2-wrapper@2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.0.5.tgz#d4464509df69b6f82125b6cb337dbb80101f406c" @@ -10139,7 +10018,7 @@ is-text-path@^1.0.1: dependencies: text-extensions "^1.0.0" -is-typedarray@^1.0.0, is-typedarray@~1.0.0: +is-typedarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== @@ -10218,11 +10097,6 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - istanbul-lib-coverage@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" @@ -10881,11 +10755,6 @@ jsbarcode@^3.8.0: resolved "https://registry.yarnpkg.com/jsbarcode/-/jsbarcode-3.11.0.tgz#20623e008b101ef45d0cce9c8022cdf49be28547" integrity sha512-/ozCd7wsa+VIHo9sUc03HneVEQrH7cVWfJolUT/WOW1m8mJ2e3iYZje6C9X3LFHdczlesqFHRpxLtbVsNtjyow== -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - jsc-android@^250230.2.1: version "250230.2.1" resolved "https://registry.yarnpkg.com/jsc-android/-/jsc-android-250230.2.1.tgz#3790313a970586a03ab0ad47defbc84df54f1b83" @@ -11034,11 +10903,6 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - json-set-map@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/json-set-map/-/json-set-map-1.1.2.tgz#536cbc6549d06e8af11f76cb4fbd441ed2389e6e" @@ -11049,7 +10913,7 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: +json-stringify-safe@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= @@ -11120,16 +10984,6 @@ jsonwebtoken@^8.4.0: ms "^2.1.1" semver "^5.6.0" -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.2.0" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82" @@ -11385,11 +11239,6 @@ lodash.find@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" integrity sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E= -lodash.flatten@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= - lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" @@ -12505,7 +12354,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@~2.1.19: +mime-types@^2.1.12: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== @@ -12855,7 +12704,7 @@ node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0, node- dependencies: whatwg-url "^5.0.0" -node-fetch@^1.0.1, node-fetch@^1.7.1: +node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== @@ -13030,11 +12879,6 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - ob1@0.58.0: version "0.58.0" resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.58.0.tgz#484a1e9a63a8b79d9ea6f3a83b2a42110faac973" @@ -13782,14 +13626,6 @@ pirates@^4.0.5: resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== -pivotaljs@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pivotaljs/-/pivotaljs-1.0.3.tgz#fafd4bac7bb921ca4509e6e768f3c10772a1f34f" - integrity sha1-+v1LrHu5IcpFCebnaPPBB3Kh808= - dependencies: - request latest - underscore latest - pkg-dir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" @@ -14044,7 +13880,7 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.28, psl@^1.1.33: +psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== @@ -14086,11 +13922,6 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" -qs@~6.5.2: - version "6.5.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" - integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== - query-string@6.10.1: version "6.10.1" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.10.1.tgz#30b3505f6fca741d5ae541964d1b3ae9dc2a0de8" @@ -15251,32 +15082,6 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request@latest: - version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -15534,7 +15339,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -16011,21 +15816,6 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" - stable@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" @@ -16686,14 +16476,6 @@ tough-cookie@^4.0.0: punycode "^2.1.1" universalify "^0.1.2" -tough-cookie@~2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" - integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== - dependencies: - psl "^1.1.28" - punycode "^2.1.1" - tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -16811,23 +16593,11 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - tween-functions@^1.0.1: version "1.2.0" resolved "https://registry.yarnpkg.com/tween-functions/-/tween-functions-1.2.0.tgz#1ae3a50e7c60bb3def774eac707acbca73bbc3ff" integrity sha1-GuOlDnxguz3vd06scHrLynO7w/8= -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -16992,7 +16762,7 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -underscore@latest, underscore@~1.10.2: +underscore@~1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.10.2.tgz#73d6aa3668f3188e4adb0f1943bd12cfd7efaaaf" integrity sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg== @@ -17268,7 +17038,7 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^3.0.1, uuid@^3.1.0, uuid@^3.3.2: +uuid@^3.0.1, uuid@^3.1.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -17315,15 +17085,6 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - vfile-message@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.0.2.tgz#db7eaebe7fecb853010f2ef1664427f52baf8f74"