UNPKG

@shopware-ag/gh-project-automation

Version:

Project automation for shopware

1,187 lines (1,173 loc) 39.9 kB
'use strict'; const webApi = require('@slack/web-api'); async function closeIssue(toolkit, issueId, reason = "NOT_PLANNED") { const res = await toolkit.github.graphql( /* GraphQL */ ` mutation closeIssue($issueId: ID!, $reason: IssueClosedStateReason!) { closeIssue(input: { issueId: $issueId, stateReason: $reason, }) { clientMutationId } } `, { issueId, reason } ); toolkit.core.debug(`closeIssue response: ${JSON.stringify(res)}`); } async function closePullRequest(toolkit, pullRequestId) { const res = await toolkit.github.graphql( /* GraphQL */ ` mutation closeIssue($pullRequestId: ID!) { closePullRequest(input: { pullRequestId: $pullRequestId }) { clientMutationId } } `, { pullRequestId } ); toolkit.core.debug(`closePullRequest response: ${JSON.stringify(res)}`); } async function getLabelByName(toolkit, repository, labelName) { const res = await toolkit.github.graphql( /* GraphQL */ ` query getLabelId($repository: String!, $labelName: String!) { repository(owner: "shopware", name: $repository) { label(name: $labelName) { id name url description color } } } `, { repository, labelName } ); return res.repository.label; } async function addLabelToLabelable(toolkit, labelId, labelableId) { const res = await toolkit.github.graphql( /* GraphQL */ ` mutation addLabels($labelId: ID!, $labelableId: ID!) { addLabelsToLabelable(input: { labelIds: [$labelId], labelableId: $labelableId }) { clientMutationId } } `, { labelId, labelableId } ); toolkit.core.debug(`addLabelToIssue response: ${JSON.stringify(res)}`); } async function findIssueWithProjectItems(toolkit, number) { const res = await toolkit.github.graphql( /* GraphQL */ ` query findIssueWithProjectItems($number: Int!) { repository(owner: "shopware", name: "shopware") { issue(number: $number) { projectItems(first: 20) { nodes { id project { number } fieldValueByName(name: "Status") { ... on ProjectV2ItemFieldSingleSelectValue { name } } } } id number } } } `, { number } ); toolkit.core.debug(`findIssueInProject response: ${JSON.stringify(res)}`); return { node_id: res.repository.issue.id, number: res.repository.issue.number, projectItems: res.repository.issue.projectItems.nodes }; } async function findPRWithProjectItems(toolkit, number) { const res = await toolkit.github.graphql( /* GraphQL */ ` query findPRWithProjectItems($number: Int!) { repository(owner: "shopware", name: "shopware") { pullRequest(number: $number) { projectItems(first: 20) { nodes { project { number } fieldValueByName(name: "Status") { ... on ProjectV2ItemFieldSingleSelectValue { name } } } } id number } } } `, { number } ); toolkit.core.debug(`findIssueInProject response: ${JSON.stringify(res)}`); return { node_id: res.repository.pullRequest.id, number: res.repository.pullRequest.number, projectItems: res.repository.pullRequest.projectItems.nodes }; } async function setFieldValue(toolkit, data) { const res = await toolkit.github.graphql( /* GraphQL */ ` mutation setFieldValue($projectId: ID!, $itemId: ID!, $fieldId: ID!, $valueId: String!) { updateProjectV2ItemFieldValue(input: { projectId: $projectId, itemId: $itemId, fieldId: $fieldId, value: {singleSelectOptionId: $valueId} }) { projectV2Item { id } } } `, data ); toolkit.core.debug(`setFieldValue response: ${JSON.stringify(res)}`); return res; } async function getProjectInfo(toolkit, data) { const res = await toolkit.github.graphql( /* GraphQL */ ` query getProjectInfo($organization: String!, $projectNumber: Int!) { organization(login: $organization) { projectV2(number: $projectNumber) { id title fields(first: 20) { nodes { ... on ProjectV2SingleSelectField { id name options { id name } } } } } } } `, { organization: data.organization ?? "shopware", projectNumber: data.number } ); toolkit.core.debug(`getProjectInfo response: ${JSON.stringify(res)}`); const project = res.organization.projectV2; return { node_id: project.id, title: project.title, fields: project.fields.nodes }; } async function addProjectItem(toolkit, data) { const res = await toolkit.github.graphql( /* GraphQL */ ` mutation addProjectItem($projectId: ID!, $contentId: ID!) { addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) { item { id } } } `, { projectId: data.projectId, contentId: data.issueId } ); toolkit.core.debug(`addCard response: ${JSON.stringify(res)}`); return { node_id: res.addProjectV2ItemById.item.id }; } async function getProjectIdByNumber(toolkit, number, organization = "shopware") { const res = await toolkit.github.graphql( /* GraphQL */ ` query getProjectIdByNumber($organization: String!, $number: Int!) { organization(login: $organization) { projectV2(number: $number) { id } } } `, { organization, number } ); return res.organization.projectV2.id; } async function getIssuesByProject(toolkit, projectId, cursor = null, carry = null) { const res = await toolkit.github.graphql( /* GraphQL */ ` query getIssuesByProject($projectId: ID!, $count: Int, $cursor: String) { node(id: $projectId) { ... on ProjectV2 { items(first: $count, after: $cursor) { pageInfo { startCursor endCursor hasPreviousPage hasNextPage } nodes { fieldValueByName(name: "Status") { ... on ProjectV2ItemFieldSingleSelectValue { name } } content { ... on Issue { id title number url issueType { name } } } } } } } } `, { projectId, count: 100, cursor } ); const issues = res.node.items.nodes.map((item) => { return { id: item.content.id, title: item.content.title, number: item.content.number, url: item.content.url, status: item.fieldValueByName?.name, labels: [], type: item.content?.issueType?.name }; }); if (res.node.items.pageInfo.hasNextPage) { return await getIssuesByProject(toolkit, projectId, res.node.items.pageInfo.endCursor, [...issues, ...carry ?? []]); } else { return [...issues, ...carry ?? []]; } } async function getCommentsForIssue(toolkit, issueId, cursor = null, carry = null) { const res = await toolkit.github.graphql( /* GraphQL */ ` query getCommentsForIssue($issueId: ID!, $count: Int, $cursor: String) { node(id: $issueId) { ... on Issue { comments(first: $count, after: $cursor) { pageInfo { startCursor endCursor hasPreviousPage hasNextPage } nodes { id author { login } body } } } } } `, { issueId, count: 100, cursor } ); const comments = res.node.comments.nodes; if (res.node.comments.pageInfo.hasNextPage) { return await getCommentsForIssue(toolkit, issueId, res.node.comments.pageInfo.endCursor, [...comments, ...carry ?? []]); } else { return [...comments, ...carry ?? []]; } } async function getPullRequests(toolkit, searchQuery) { const pullRequests = await toolkit.github.graphql( /* GraphQL */ ` query findPullRequests($searchQuery: String!) { search(query: $searchQuery, type: ISSUE, first: 50) { pageInfo { startCursor endCursor hasPreviousPage hasNextPage } nodes { ... on PullRequest { id title number url repository { owner { login } name } assignees(first: 50) { nodes { login } } reviewRequests(first: 50) { nodes { requestedReviewer { ... on User { login } ... on Team { name } } } } closingIssuesReferences(first: 1) { nodes { id title number url repository { owner { login } name } } } } } } }`, { searchQuery } ).then((res) => res.search.nodes); return pullRequests; } async function addComment(toolkit, issueId, commentBody) { const comment = await toolkit.github.graphql( /* GraphQL */ ` mutation addComment($issueId: ID!, $body: String!) { addComment(input: { subjectId: $issueId, body: $body }) { commentEdge { node { id author { login } body url } } } } `, { issueId, body: commentBody } ).then((res) => res.addComment.commentEdge.node); if (!comment || !comment.id) { throw new Error(`Failed to create comment: ${JSON.stringify(comment)}`); } return comment; } async function getVerifiedDomainEmails(toolkit, login, organization) { const res = await toolkit.github.graphql( /* GraphQL */ ` query getVerifiedDomainEmails($login: String!, $organization: String!) { user(login: $login) { organizationVerifiedDomainEmails(login: $organization) } } `, { login, organization } ); return res.user.organizationVerifiedDomainEmails; } const jiraHost = "shopware.atlassian.net"; const jiraBaseUrl = `https://${jiraHost}/rest/api/3`; async function callJiraApi(toolkit, endpoint, requestBody) { return await toolkit.fetch(`${jiraBaseUrl}${endpoint}`, { method: "POST", headers: { "Authorization": `Basic ${Buffer.from( `${process.env.JIRA_USERNAME}:${process.env.JIRA_API_TOKEN}` ).toString("base64")}`, "Accept": "application/json", "Content-Type": "application/json" }, body: JSON.stringify(requestBody) }).then((res) => res.json()); } class SlackClient { webClient; constructor(token) { this.webClient = new webApi.WebClient(token); } async getUserByEmail(email) { const response = await this.webClient.users.lookupByEmail({ email }); return response.user?.id || null; } async sendIMToUser(userId, message) { await this.webClient.chat.postMessage({ channel: userId, text: message, blocks: [ { type: "markdown", text: message } ] }); } } function isDryRun() { return process.env.DRY_RUN === "true" || process.env.DRY_RUN === "1"; } async function getOldBranches(toolkit, repo, excludeRegex = "", organization = "shopware") { const DAYS_UNTIL_STALE = 6 * 30; const regex = new RegExp(excludeRegex); toolkit.core.info(`Getting branches for ${organization}/${repo}`); const branches = []; let currentCursor = null; while (true) { const res = await toolkit.github.graphql( /* GraphQL */ ` query getBranches( $cursor: String $organization: String! $repository: String! ) { repository(owner: $organization, name: $repository) { refs(first: 100, after: $cursor, refPrefix: "refs/heads/") { pageInfo { startCursor endCursor hasNextPage } nodes { name target { ... on Commit { committedDate } } associatedPullRequests(first: 100) { nodes { number } } } } } } `, { cursor: currentCursor, repository: repo, organization } ); branches.push(...res.repository.refs.nodes); if (!res.repository.refs.pageInfo.hasNextPage) { break; } currentCursor = res.repository.refs.pageInfo.endCursor; } const today = /* @__PURE__ */ new Date(); const cmpDate = new Date((/* @__PURE__ */ new Date()).setDate(today.getDate() - DAYS_UNTIL_STALE)); const oldBranches = []; for (const branch of branches.filter((x) => x.associatedPullRequests.nodes.length == 0)) { if (excludeRegex != "" && regex.test(branch.name)) { continue; } const lastUpdate = new Date(branch.target.committedDate); if (lastUpdate < cmpDate) { oldBranches.push(branch.name); toolkit.core.debug(`${branch.name} : ${lastUpdate}`); } } return oldBranches; } async function cleanupBranches(toolkit, repo, organization = "shopware") { const branches = await getOldBranches(toolkit, repo, organization); if (!branches) { toolkit.core.error("No old branches found!"); return; } for (const branch of branches) { if (isDryRun()) { toolkit.core.info(`Would delete ${branch}`); continue; } toolkit.core.info(`Deleting ${branch}...`); toolkit.github.rest.git.deleteRef({ owner: organization, repo, ref: branch }); } } const docIssueReference = Buffer.from("doc-issue-created").toString("base64"); async function getDevelopmentIssueForPullRequest(toolkit, repo, pullRequestNumber, pullRequestHead, pullRequestAssignee) { const pullRequests = await getPullRequests( toolkit, `repo:${repo} is:pr assignee:${pullRequestAssignee} head:${pullRequestHead}` ); const matchingPullRequests = pullRequests.filter((pr) => pr.number === pullRequestNumber); const developmentIssue = matchingPullRequests.length > 0 ? matchingPullRequests[0]?.closingIssuesReferences?.nodes[0] : null; if (developmentIssue) { return { id: developmentIssue.id, title: developmentIssue.title, number: developmentIssue.number, url: developmentIssue.url, labels: [], owner: developmentIssue.repository.owner.login, repository: developmentIssue.repository.name }; } else { toolkit.core.info(`No development issue found for PR #${pullRequestNumber}`); return null; } } async function getEpicsInProgressByProject(toolkit, projectId) { const res = await getIssuesByProject(toolkit, projectId); return res.filter((item) => { return item.status?.toLowerCase() === "in progress" && item.type?.toLowerCase() === "epic"; }); } async function createDocIssueComment(toolkit, issueId, docIssueKey, content = null) { const commentBody = `${content ?? "A documentation Task has been created for this issue:"} [${docIssueKey}](https://${jiraHost}/browse/${docIssueKey}). <!-- ${docIssueReference} -->`; const comment = await addComment(toolkit, issueId, commentBody); toolkit.core.info(`Created documentation issue reference comment: ${comment.url}`); return comment; } async function hasDocIssueComment(toolkit, issueId) { const comments = await getCommentsForIssue(toolkit, issueId); for (const comment of comments) { if (referencesDocIssue(comment)) { return true; } } return false; } function referencesDocIssue(comment) { return comment.body.includes(docIssueReference); } async function manageNeedsTriageLabel(toolkit, labelable, needsTriageLabel, dryRun) { const labels = labelable.labels.nodes.map((label) => label.name); const hasNeedsTriage = labels.includes("needs-triage"); const hasDomainOrServiceLabel = labels.some( (label) => label.startsWith("domain/") || label.startsWith("service/") ); if (hasDomainOrServiceLabel && hasNeedsTriage) { if (dryRun) { toolkit.core.info(`Would remove needs-triage label from #${labelable.number}: ${labelable.title} (has domain/service label)`); } else { await toolkit.github.graphql( /* GraphQL */ ` mutation removeLabel($labelableId: ID!, $labelIds: [ID!]!) { removeLabelsFromLabelable(input: { labelableId: $labelableId, labelIds: $labelIds }) { clientMutationId } } `, { labelableId: labelable.id, labelIds: [needsTriageLabel.id] } ); toolkit.core.info(`Removed needs-triage label from #${labelable.number}: ${labelable.title} (has domain/service label)`); } } else if (!hasNeedsTriage && !hasDomainOrServiceLabel) { if (dryRun) { toolkit.core.info(`Would add needs-triage label to #${labelable.number}: ${labelable.title}`); } else { await addLabelToLabelable(toolkit, needsTriageLabel.id, labelable.id); toolkit.core.info(`Added needs-triage label to #${labelable.number}: ${labelable.title}`); } } } async function cleanupNeedsTriage(toolkit, dryRun = false) { const needsTriageLabel = await getLabelByName(toolkit, "shopware", "needs-triage"); if (!needsTriageLabel) { throw new Error("Couldn't find the needs-triage label"); } let processedItems = 0; let hasNextPageIssues = true; let cursorIssues = null; while (hasNextPageIssues) { const res = await toolkit.github.graphql( /* GraphQL */ ` query getOpenIssues($cursor: String) { repository(owner: "shopware", name: "shopware") { issues(first: 100, after: $cursor, states: OPEN) { pageInfo { hasNextPage endCursor } nodes { id number title labels(first: 20) { nodes { name } } } } } } `, { cursor: cursorIssues } ); const issues = res.repository.issues?.nodes ?? []; hasNextPageIssues = res.repository.issues?.pageInfo.hasNextPage ?? false; cursorIssues = res.repository.issues?.pageInfo.endCursor ?? null; for (const item of issues) { await manageNeedsTriageLabel(toolkit, item, needsTriageLabel, dryRun); } processedItems += issues.length; toolkit.core.info(`Processed ${processedItems} items so far...`); } let hasNextPagePRs = true; let cursorPRs = null; while (hasNextPagePRs) { const res = await toolkit.github.graphql( /* GraphQL */ ` query getOpenPRs($cursor: String) { repository(owner: "shopware", name: "shopware") { pullRequests(first: 100, after: $cursor, states: OPEN) { pageInfo { hasNextPage endCursor } nodes { id number title labels(first: 20) { nodes { name } } } } } } `, { cursor: cursorPRs } ); const pullRequests = res.repository.pullRequests?.nodes ?? []; hasNextPagePRs = res.repository.pullRequests?.pageInfo.hasNextPage ?? false; cursorPRs = res.repository.pullRequests?.pageInfo.endCursor ?? null; for (const item of pullRequests) { await manageNeedsTriageLabel(toolkit, item, needsTriageLabel, dryRun); } processedItems += pullRequests.length; toolkit.core.info(`Processed ${processedItems} items so far...`); } toolkit.core.info(`Finished processing ${processedItems} items`); } async function findWithProjectItems(toolkit) { if (toolkit.context.payload.issue) { return await findIssueWithProjectItems(toolkit, toolkit.context.payload.issue.number); } else if (toolkit.context.payload.pull_request) { return await findPRWithProjectItems(toolkit, toolkit.context.payload.pull_request.number); } else { throw new Error("only issue and pull_request events are supported"); } } async function createDocumentationTask(toolkit, issue, documentationProjectId = 11806, description = null) { const requestBody = buildDocumentationTaskRequest(issue, documentationProjectId, description); const docTask = await callJiraApi(toolkit, "/issue", requestBody); if (!docTask || !docTask.key) { throw new Error(`Failed to create documentation task: ${JSON.stringify(docTask)}`); } toolkit.core.info(`Created documentation task in JIRA: https://${jiraHost}/browse/${docTask.key}`); return docTask; } function buildDocumentationTaskRequest(issue, documentationProjectId, description) { return { fields: { project: { id: documentationProjectId }, summary: `${issue.title}`, description: buildDocumentationDescription(issue, description), issuetype: { name: "Task" } } }; } function buildDocumentationDescription(issue, description) { return { version: 1, type: "doc", content: [ { type: "paragraph", content: [ { type: "text", text: description ?? `This issue was created automatically.` }, { type: "text", text: ` Please refer to the following links and/or related issues for more information:` } ] }, { type: "bulletList", content: [ { type: "listItem", content: [ { type: "paragraph", content: [ { type: "text", text: issue.url, marks: [ { type: "link", attrs: { href: issue.url } } ] } ] } ] } ] } ] }; } async function setStatusInProjects(toolkit, props) { const issue = await findWithProjectItems(toolkit); for (const i in issue.projectItems) { const item = issue.projectItems[i]; const projectInfo = await getProjectInfo(toolkit, { number: item.project.number }); const statusField = projectInfo.fields.find((field) => field.name == "Status"); const toStatusOption = statusField?.options.find((x) => x.name.toLowerCase() === props.toStatus.toLowerCase()); if (!toStatusOption) { toolkit.core.debug(`Option "${props.toStatus}" not found in project ${item.project.number}`); continue; } if (props.fromStatus instanceof RegExp) { if (!item.fieldValueByName.name.match(props.fromStatus)) { toolkit.core.debug(`skipping: issue/pr ${issue.number} status ${item.fieldValueByName} did not match ${props.fromStatus} in project ${item.project.number}`); continue; } } else if (props.fromStatus && item.fieldValueByName.name.toLowerCase() !== props.fromStatus.toLowerCase()) { toolkit.core.debug(`skipping: issue/pr ${issue.number} status != ${props.fromStatus} in project ${item.project.number}`); continue; } toolkit.core.info(`get item in project ${item.project.number} for issue/pr ${issue.number}`); const itemId = (await addProjectItem(toolkit, { projectId: projectInfo.node_id, issueId: issue.node_id })).node_id; await setFieldValue(toolkit, { projectId: projectInfo.node_id, itemId, fieldId: statusField.id, valueId: toStatusOption.id }); } } async function syncPriorities(toolkit, excludeList = []) { const issue = toolkit.context.payload.issue; toolkit.core.debug(`Issue node ID: ${issue.node_id}`); const priorityLabel = issue.labels.find( (label) => label.name.startsWith("priority/") )?.name; if (!priorityLabel) { return; } toolkit.core.info(`Found priority label: ${priorityLabel}`); const priority = priorityLabel.split("/")[1]; toolkit.core.info(`Priority: ${priority}`); const issueWithProjectItems = await findIssueWithProjectItems(toolkit, issue.number); for (const projectItem of issueWithProjectItems.projectItems) { if (excludeList.includes(projectItem.project.number)) { toolkit.core.info(`The project number ${projectItem.project.number} is on the excludeList. skipping...`); continue; } const projectInfo = await getProjectInfo(toolkit, { number: projectItem.project.number }); const priorityField = projectInfo.fields.find((field) => field.name == "Priority"); if (!priorityField) { toolkit.core.info(`${projectInfo.title} doesn't have a priority field. skipping...`); continue; } const priorityOption = priorityField?.options.find((option) => option.name == priority); if (!priorityOption) { toolkit.core.info(`${projectInfo.title} doesn't have the priority option ${priority}. skipping...`); continue; } toolkit.core.info(`Setting priority for issue ${issue.number} on ${projectInfo.title} to ${priority}`); await setFieldValue(toolkit, { projectId: projectInfo.node_id, itemId: projectItem.id, fieldId: priorityField.id, valueId: priorityOption.id }); } } async function createDocumentationTasksForProjects(toolkit, projectNumbers, organization = "shopware", documentationProjectId = 11806, description = null, comment = null) { for (const projectNumber of projectNumbers) { const projectId = await getProjectIdByNumber(toolkit, projectNumber, organization); const epicsInProgress = await getEpicsInProgressByProject(toolkit, projectId); for (const epic of epicsInProgress) { if (await hasDocIssueComment(toolkit, epic.id)) { toolkit.core.info(`Skipping issue ${epic.url} because it already has a documentation issue reference.`); continue; } const docTask = await createDocumentationTask(toolkit, epic, documentationProjectId, description); await createDocIssueComment(toolkit, epic.id, docTask.key, comment); } } } async function markStaleIssues(toolkit, projectNumber, dryRun) { const DAYS_UNTIL_STALE = 180; const now = /* @__PURE__ */ new Date(); now.setDate(now.getDate() - DAYS_UNTIL_STALE); const staleDate = now.toISOString().split("T")[0]; switch (Number(projectNumber)) { case 27: { const query = ( /* GraphQL */ ` query searchClosableIssues { search( type: ISSUE first: 100 query: "repo:shopware/shopware is:issue state:open project:shopware/27 label:priority/low -label:AboutToClose -label:DoNotClose created:<=$staleDate" ) { pageInfo { hasNextPage endCursor } edges { node { ... on Issue { id title number url parent { issueType { name } } labels(first: 20) { nodes { name } } projectItems(first: 10) { nodes { project { number } fieldValueByName(name: "Status") { ... on ProjectV2ItemFieldSingleSelectValue { name } } } } } } } } } ` ); const res = await toolkit.github.graphql(query.replace("$staleDate", staleDate), { headers: { "GraphQL-Features": "issue_types" } }); const issues = res.search.edges; for (const issueNode of issues) { const issue = issueNode.node; const parentIssueType = issue.parent?.issueType.name; const labels = issue.labels.nodes; const priorityLabel = labels.find( (label) => label.name.startsWith("priority/") )?.name; const statusInProject = issue.projectItems.nodes.find((projectItem) => projectItem.project.number == 27)?.fieldValueByName.name; if ((priorityLabel && priorityLabel.split("/")[1] === "low" || statusInProject == "Backlog") && parentIssueType != "Epic") { if (dryRun) { toolkit.core.info(`Would set "${issue.title}" (${issue.url}) to AboutToClose`); continue; } const aboutToCloseLabel = await getLabelByName(toolkit, "shopware", "AboutToClose"); if (!aboutToCloseLabel) { throw Error("Couldn't find the AboutToClose label"); } await addLabelToLabelable(toolkit, issue.id, aboutToCloseLabel.id); } } break; } default: throw new Error(`There is no query for the project with the number ${projectNumber}`); } } async function closeStaleIssues(toolkit, dryRun) { const DAYS_UNTIL_CLOSE = 30; const now = /* @__PURE__ */ new Date(); now.setDate(now.getDate() - DAYS_UNTIL_CLOSE); const closeDate = now.toISOString().split("T")[0]; const query = ( /* GraphQL */ ` query searchStaleIssues { search( type: ISSUE first: 100 query: "repo:shopware/shopware is:issue state:open label:AboutToClose updated:<=$closeDate" ) { pageInfo { hasNextPage endCursor } edges { node { ... on Issue { id title number url parent { issueType { name } } labels(first: 20) { nodes { name } } projectItems(first: 10) { nodes { fieldValueByName(name: "Status") { ... on ProjectV2ItemFieldSingleSelectValue { name } } } } } } } } } ` ); const res = await toolkit.github.graphql(query.replace("$closeDate", closeDate), { headers: { "GraphQL-Features": "issue_types" } }); const issues = res.search.edges; for (const issueNode of issues) { const issue = issueNode.node; if (dryRun) { toolkit.core.info(`Would close "${issue.title}" (${issue.url})`); continue; } await closeIssue(toolkit, issue.id); } } async function manageOldPullRequests(toolkit, organization = "shopware", days = 7, close = false) { const pullRequests = await getPullRequests( toolkit, `org:${organization} is:pr is:open draft:false updated:<${new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString()}` ); const closeMsg = `This pull request has been closed automatically. If you would like to continue working on it, please feel free to re-open it!`; for (const pr of pullRequests) { const assignee = pr.assignees.nodes[0]; if (!assignee) { toolkit.core.debug(`Pull request ${pr.repository.owner.login}/${pr.repository.name}#${pr.number} has no assignee, skipping.`); continue; } const emails = await getVerifiedDomainEmails(toolkit, assignee.login, organization); if (emails.length < 1) { continue; } if (close) { if (isDryRun()) { toolkit.core.info(`[DRY_RUN] ${pr.url}`); } else { await closePullRequest(toolkit, pr.id); await addComment(toolkit, pr.id, closeMsg); } } } } async function sendSlackMessageForEMail(toolkit, emails, message) { if (process.env.SLACK_TOKEN === void 0) { throw new Error("Unauthorized. SLACK_TOKEN environment variable not provided."); } const slackClient = new SlackClient(process.env.SLACK_TOKEN); for (const email of emails) { if (!email || email.trim() === "") { continue; } const userId = await slackClient.getUserByEmail(email); if (!userId) { continue; } await slackClient.sendIMToUser(userId, message); return; } toolkit.core.warning(`No valid Slack user found for emails, can't send message.`); } exports.SlackClient = SlackClient; exports.addComment = addComment; exports.addLabelToLabelable = addLabelToLabelable; exports.addProjectItem = addProjectItem; exports.callJiraApi = callJiraApi; exports.cleanupBranches = cleanupBranches; exports.cleanupNeedsTriage = cleanupNeedsTriage; exports.closeIssue = closeIssue; exports.closePullRequest = closePullRequest; exports.closeStaleIssues = closeStaleIssues; exports.createDocIssueComment = createDocIssueComment; exports.createDocumentationTask = createDocumentationTask; exports.createDocumentationTasksForProjects = createDocumentationTasksForProjects; exports.docIssueReference = docIssueReference; exports.findIssueWithProjectItems = findIssueWithProjectItems; exports.findPRWithProjectItems = findPRWithProjectItems; exports.findWithProjectItems = findWithProjectItems; exports.getCommentsForIssue = getCommentsForIssue; exports.getDevelopmentIssueForPullRequest = getDevelopmentIssueForPullRequest; exports.getEpicsInProgressByProject = getEpicsInProgressByProject; exports.getIssuesByProject = getIssuesByProject; exports.getLabelByName = getLabelByName; exports.getOldBranches = getOldBranches; exports.getProjectIdByNumber = getProjectIdByNumber; exports.getProjectInfo = getProjectInfo; exports.getPullRequests = getPullRequests; exports.getVerifiedDomainEmails = getVerifiedDomainEmails; exports.hasDocIssueComment = hasDocIssueComment; exports.jiraBaseUrl = jiraBaseUrl; exports.jiraHost = jiraHost; exports.manageOldPullRequests = manageOldPullRequests; exports.markStaleIssues = markStaleIssues; exports.referencesDocIssue = referencesDocIssue; exports.sendSlackMessageForEMail = sendSlackMessageForEMail; exports.setFieldValue = setFieldValue; exports.setStatusInProjects = setStatusInProjects; exports.syncPriorities = syncPriorities; //# sourceMappingURL=index.cjs.map