Skip to content
Permalink
 
 
Cannot retrieve contributors at this time
248 lines (211 sloc) 7.57 KB
import { danger, warn, fail, GitHubCommit, markdown } from "danger"
import yarn from "danger-plugin-yarn"
// "Highlight package dependencies on Node projects"
const rfc1 = async () => {
await yarn()
}
import spellcheck from "danger-plugin-spellcheck"
// "Keep our Markdown documents awesome",
const rfc2 = async () => {
await spellcheck({ settings: "artsy/peril-settings@spellcheck.json" })
}
// "No PR is too small to warrant a paragraph or two of summary"
// https://github.com/artsy/peril-settings/issues/5
export const rfc5 = () => {
const pr = danger.github.pr
if (pr.body === null || pr.body.length === 0) {
fail("Please add a description to your PR.")
}
}
// "Hook commit contexts to GitHub PR/Issue labels"
// https://github.com/artsy/peril-settings/issues/7
export const rfc7 = async () => {
const pr = danger.github.thisPR
const commitLabels: string[] = danger.git.commits
.map((c) => c.message)
.filter((m) => m.startsWith("[") && m.includes("]"))
.map((m) => (m.match(/\[(.*)\]/) as any)[1]) // Guaranteed to match based on filter above.
if (commitLabels.length > 0) {
const api = danger.github.api
const githubLabels = await api.issues.listLabelsForRepo({ owner: pr.owner, repo: pr.repo })
const matchingLabels = githubLabels.data
.map((l) => l.name)
.filter((l) => commitLabels.find((cl) => l === cl))
.filter((l) => !danger.github.issue.labels.find((label) => label.name === l))
if (matchingLabels.length > 0) {
await api.issues.addLabels({ owner: pr.owner, repo: pr.repo, number: pr.number, labels: matchingLabels })
}
}
}
// Always ensure we assign someone, so that our Slackbot work correctly
// https://github.com/artsy/peril-settings/issues/13
export const rfc13 = async () => {
const pr = danger.github.pr
const isRenovate = pr.user.login.toLowerCase().includes("renovate")
const wipPR = pr.title.includes("WIP ") || pr.title.includes("[WIP]")
if (!isRenovate && !wipPR && pr.assignee === null) {
// Validate they are in the org, before asking to assign
try {
await danger.github.api.orgs.checkMembership({ org: "artsy", username: danger.github.pr.user.login })
warn("Please assign someone to merge this PR, and optionally include people who should review.")
} catch (error) {
// They couldn't assign someone if they tried.
return console.log("Sender does not have permission to assign to this PR")
}
}
}
// Require changelog entries on PRs with code changes
// https://github.com/artsy/peril-settings/issues/16
export const rfc16 = async () => {
const pr = danger.github.pr
if (pr.base.repo.name === "eigen") {
console.log("In eigen we don't want this. We added a checkbox in our PR template.")
return
}
if (pr.body.includes("#trivial")) {
console.log("Skipping changelog check because the PR is marked as trivial")
return
}
const changelogs = ["CHANGELOG.md", "changelog.md", "CHANGELOG.yml"]
const isOpen = danger.github.pr.state === "open"
// Get all the files in the root folder of the repo
// e.g. https://api.github.com/repos/artsy/eigen/git/trees/master
const rootContentsAPI = await danger.github.api.git.getTree({
owner: pr.base.user.login,
repo: pr.base.repo.name,
tree_sha: pr.base.sha,
})
const rootContents = rootContentsAPI.data
// We have some auto-generated Changelogs
const isAutoGenerated = rootContents.tree.find((file: { path: string }) => file.path == ".autorc")
if (isAutoGenerated) {
console.log("Changelog is auto generated, so skipping any Changelog warnings")
return
}
const hasChangelog = rootContents.tree.find((file: { path: string }) => changelogs.includes(file.path))
if (isOpen && hasChangelog) {
const files = [...danger.git.modified_files, ...danger.git.created_files]
const hasCodeChanges = files.find((file) => !file.match(/(test|spec)/i))
const hasChangelogChanges = files.find((file) => changelogs.includes(file))
if (hasCodeChanges && !hasChangelogChanges) {
warn(
"It looks like code was changed without adding anything to the Changelog.<br/>You can add #trivial in the PR body to skip the check."
)
}
}
}
// Warn PR authors if they assign more than one person to a PR
// https://github.com/artsy/README/issues/177
export const rfc177 = () => {
const pr = danger.github.pr
if (pr.assignees && pr.assignees.length > 1) {
warn("Please only assign one person to a PR")
}
}
// RFC 238: https://github.com/artsy/README/issues/238
// Summarize deploy PR's with included PR's
export const deploySummary = async () => {
// Returns `true` if this is a PR to the `release` branch.
const isRelease = () => {
return danger.github.pr.base.ref === "release"
}
// Map of PR's included in the deploy.
// (will be outputted as a comment)
interface PRInfoMap {
[prNumber: number]: PRInfo
}
// Info per PR that is included
interface PRInfo {
title: string
url: string
}
// Memoized map of PR's fetched, and their info.
const prMap: PRInfoMap = {}
// For a given commit, will build a GraphQL fragment to retrieve
// the associated pull request.
// These be combined into one query.
const fragmentForCommit = (commit: GitHubCommit) => {
const sha = commit.sha
const fragment = `
sha_${sha}: object(expression: "${sha}") {
... on Commit {
associatedPullRequests(first:1) {
edges {
node {
title
url
number
}
}
}
}
}
`
return fragment
}
if (!isRelease()) return
const repo = danger.github.thisPR.repo
const owner = danger.github.thisPR.owner
const fragments = danger.github.commits.map((c) => fragmentForCommit(c)).join("")
const query = `
{
repository(owner: "${owner}", name: "${repo}") {
${fragments}
}
}
`
const resp = await danger.github.api.request({
method: "POST",
url: "/graphql",
query,
})
const { data } = resp
const info = data.data.repository
Object.entries(info).forEach(([_sha, pulls]: [string, any]) => {
if (
pulls.associatedPullRequests &&
pulls.associatedPullRequests.edges &&
pulls.associatedPullRequests.edges.length
) {
const pull = pulls.associatedPullRequests.edges[0].node
if (prMap[pull.number]) return
prMap[pull.number] = {
title: pull.title,
url: pull.url,
}
}
})
if (!Object.keys(prMap).length) return
const message =
"### This deploy contains the following PRs:\n\n" +
Object.entries(prMap)
.map(([_number, info]) => {
return `- ${info.title} (${info.url})\n`
})
.join("")
return markdown(message)
}
// Nudge PR authors to use semantic commit formatting
// https://github.com/artsy/README/issues/327
export const rfc327 = () => {
const semanticFormat = /^(fix|feat|build|chore|ci|docs|style|refactor|perf|test)(?:\(.+\))?!?:.+$/
const pr = danger.github.pr
const repoName = pr.base.repo.name
if (["peril-settings", "volt"].includes(repoName) && !semanticFormat.test(pr.title)) {
return markdown(
"Hi there! :wave:\n\nWe're trialing semantic commit formatting which has not been detected in your PR title.\n\nRefer to [this RFC](https://github.com/artsy/README/issues/327#issuecomment-698842527) and [Conventional Commits](https://www.conventionalcommits.org) for PR/commit formatting guidelines."
)
}
}
// The default run
export default async () => {
rfc1()
await rfc2()
rfc5()
await rfc7()
await rfc13()
await rfc16()
await rfc177()
rfc327()
await deploySummary()
}