Skip to content

Commit

Permalink
Merge pull request #93 from ghinks/feature/close-draft-pr
Browse files Browse the repository at this point in the history
  • Loading branch information
dominykas authored Jun 28, 2021
2 parents 0c185d7 + ba96374 commit b0fb895
Show file tree
Hide file tree
Showing 17 changed files with 468 additions and 11 deletions.
9 changes: 6 additions & 3 deletions .wiby.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
{
"dependents": [
{
"repository": "https://github.com/wiby-test/partial"
"repository": "https://github.com/wiby-test/partial",
"pullRequest": false
},
{
"repository": "git://github.com/wiby-test/fail"
"repository": "git://github.com/wiby-test/fail",
"pullRequest": false
},
{
"repository": "git+https://github.com/wiby-test/pass"
"repository": "git+https://github.com/wiby-test/pass",
"pullRequest": true
}
]
}
29 changes: 29 additions & 0 deletions bin/commands/close-pr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict'

const wiby = require('../..')

exports.desc = 'Use this command to close the PRs raised against your dependents. wiby will go off to the dependent’s repo and close the PRs raised that trigger jobs `package.json` pointing to your latest version (with the new changes) triggering the dependent’s CI to run.'

exports.builder = (yargs) => yargs
.option('dependent', {
desc: 'URL of a dependent',
type: 'string',
conflicts: 'config'
})
.option('config', {
desc: 'Path to the configuration file. By default it will try to load the configuration from the first file it finds in the current working directory: `.wiby.json`, `.wiby.js`',
type: 'string'
})

exports.handler = async (params) => {
const config = params.dependent
? {
dependents: [{ repository: params.dependent, pullRequest: true }]
}
: wiby.validate({ config: params.config })

const result = await wiby.closePR(config)
// TODO, something more like the result process output
const output = `${result.length} PRs closed`
console.log(output)
}
4 changes: 3 additions & 1 deletion bin/commands/result.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ exports.builder = (yargs) => yargs

exports.handler = async (params) => {
const config = params.dependent
? { dependents: [{ repository: params.dependent }] }
? {
dependents: [{ repository: params.dependent }]
}
: wiby.validate({ config: params.config })

const result = await wiby.result(config)
Expand Down
10 changes: 9 additions & 1 deletion bin/commands/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,22 @@ exports.builder = (yargs) => yargs
type: 'string',
conflicts: 'config'
})
.option('pull-request', {
desc: 'Raise a draft PR in addition to creating a branch',
alias: 'pr',
type: 'boolean',
conflicts: 'config'
})
.option('config', {
desc: 'Path to the configuration file. By default it will try to load the configuration from the first file it finds in the current working directory: `.wiby.json`, `.wiby.js`',
type: 'string'
})

exports.handler = (params) => {
const config = params.dependent
? { dependents: [{ repository: params.dependent }] }
? {
dependents: [{ repository: params.dependent, pullRequest: !!params['pull-request'] }]
}
: wiby.validate({ config: params.config })

return wiby.test(config)
Expand Down
49 changes: 49 additions & 0 deletions lib/closePR.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use strict'

const github = require('../lib/github')
const logger = require('./logger')
const context = require('./context')
const gitURLParse = require('git-url-parse')

const debug = logger('wiby:closepr')

module.exports = async ({ dependents }) => {
const closedPrs = []
for (const { repository: url, pullRequest } of dependents) {
if (pullRequest) {
const dependentPkgInfo = gitURLParse(url)
const parentBranchName = await context.getParentBranchName()
const branch = await context.getTestingBranchName(parentBranchName)
const resp = await github.getChecks(dependentPkgInfo.owner, dependentPkgInfo.name, branch)
const closedPR = await closeDependencyPR(dependentPkgInfo.owner, dependentPkgInfo.name, branch, resp.data.check_runs)
if (closedPR) {
closedPrs.push(closedPR)
}
}
}
return closedPrs
}

const closeDependencyPR = module.exports.closeDependencyPR = async function closeDependencyPR (owner, repo, branch, checkRuns) {
if (!checkRuns) {
return
}
// TODO, in reality multiple checks could exist and they may not all have passed
const prsToClose = checkRuns.reduce((acc, check) => {
if (check.status === 'completed' &&
check.conclusion === 'success' &&
check.pull_requests &&
check.pull_requests.length !== 0) {
check.pull_requests.forEach((pr) => {
if (pr.head.ref === branch) {
acc.push({
number: pr.number
})
}
})
}
return acc
}, [])
debug(`Dependent module: ${JSON.stringify(prsToClose, null, 2)}`)
return await Promise.all(prsToClose.map((pr) => github.closePR(owner, repo, pr.number)))
}
3 changes: 2 additions & 1 deletion lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const dependentSchema = joi.object({
'git',
'git+https'
]
})
}),
pullRequest: joi.boolean().optional()
}).unknown(false)

exports.schema = joi.object({
Expand Down
30 changes: 30 additions & 0 deletions lib/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ module.exports.getWibyBranches = async function (owner, repo) {
return edges.map(({ node: { branchName } }) => branchName)
}

module.exports.getDefaultBranch = async function (owner, repo) {
const resp = await graphqlWithAuth({
query: queries.getDefaultBranch,
owner: owner,
repo: repo
})
return resp.repository.defaultBranchRef.name
}

module.exports.getShas = async function getShas (owner, repo) {
const resp = await octokit.repos.listCommits({
owner,
Expand Down Expand Up @@ -136,3 +145,24 @@ module.exports.getCommitStatusesForRef = async function getCommitStatusesForRef
ref: branch
})
}

module.exports.createPR = async function createPR (owner, repo, title, head, base, draft, body) {
return octokit.pulls.create({
owner,
repo,
title,
head,
base,
draft,
body
})
}

module.exports.closePR = async function closePR (owner, repo, pullNumber) {
return octokit.rest.pulls.update({
owner,
repo,
pull_number: pullNumber,
state: 'closed'
})
}
7 changes: 7 additions & 0 deletions lib/graphql/getDefaultBranch.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
query getExistingRepoBranches($owner: String!, $repo: String!) {
repository(name: $repo, owner: $owner) {
defaultBranchRef {
name
}
}
}
2 changes: 2 additions & 0 deletions lib/graphql/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ const path = require('path')
exports.getPackageJson = fs.readFileSync(path.join(__dirname, 'getPackageJson.graphql')).toString()

exports.getWibyBranches = fs.readFileSync(path.join(__dirname, 'getWibyBranches.graphql')).toString()

exports.getDefaultBranch = fs.readFileSync(path.join(__dirname, 'getDefaultBranch.graphql')).toString()
2 changes: 2 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ exports.test = require('./test')
exports.result = require('./result')

exports.validate = require('./config').validate

exports.closePR = require('./closePR')
30 changes: 29 additions & 1 deletion lib/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ module.exports = async function ({ dependents }) {
const parentDependencyLink = await context.getDependencyLink(parentRepositoryInfo.owner, parentRepositoryInfo.name, parentBranchName)
debug('Commit URL to test:', parentDependencyLink)

for (const { repository: url } of dependents) {
for (const { repository: url, pullRequest } of dependents) {
const dependentRepositoryInfo = gitURLParse(url)
const dependentPkgJson = await github.getPackageJson(dependentRepositoryInfo.owner, dependentRepositoryInfo.name)
debug(`Dependent module: ${dependentRepositoryInfo.owner}/${dependentRepositoryInfo.name}`)
Expand All @@ -32,6 +32,9 @@ module.exports = async function ({ dependents }) {

const patchedPackageJSON = applyPatch(parentDependencyLink, parentPkgJSON.name, dependentPkgJson, parentPkgJSON.name)
await pushPatch(patchedPackageJSON, dependentRepositoryInfo.owner, dependentRepositoryInfo.name, parentPkgJSON.name, parentBranchName)
if (pullRequest) {
await createPR(dependentRepositoryInfo.owner, dependentRepositoryInfo.name, parentBranchName, parentDependencyLink)
}
}
}

Expand All @@ -58,3 +61,28 @@ async function pushPatch (dependentPkgJson, dependentOwner, dependentRepo, paren
await github.createBranch(dependentOwner, dependentRepo, commitSha, branch)
debug(`Changes pushed to https://github.com/${dependentOwner}/${dependentRepo}/blob/${branch}/package.json`)
}

const createPR = module.exports.createPR = async function createPR (dependentOwner, dependentRepo, parentBranchName, parentDependencyLink) {
const title = `Wiby changes to ${parentDependencyLink}`
const body = `Wiby has raised this PR to kick off automated jobs.
You are dependent upon ${parentDependencyLink}
`
const head = context.getTestingBranchName(parentBranchName)
const draft = true
/*
from the conversation on wiby PR 93 https://github.com/pkgjs/wiby/pull/93#discussion_r615362448
it was seen that the raising of a PR from head to main was in general ok but in the case where the
dependency feature branch does exist in the dependent then maybe detection and handle would offer a
better experience.
*/
const preExistingOnDependent = await github.getBranch(dependentOwner, dependentRepo, parentBranchName)
let result
if (preExistingOnDependent) {
result = await github.createPR(dependentOwner, dependentRepo, title, head, parentBranchName, draft, body)
} else {
const defaultBranch = await github.getDefaultBranch(dependentOwner, dependentRepo)
result = await github.createPR(dependentOwner, dependentRepo, title, head, defaultBranch, draft, body)
}
debug(`PR raised upon ${result.data.html_url}`)
return result
}
43 changes: 43 additions & 0 deletions test/cli/closePR.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict'
const tap = require('tap')
const gitFixture = require('../fixtures/git')
const childProcess = require('child_process')
const nock = require('nock')
const path = require('path')

const wibyCommand = path.join(__dirname, '..', '..', 'bin', 'wiby')
const fixturesPath = path.resolve(path.join(__dirname, '..', 'fixtures'))

tap.test('closePRs command', async (t) => {
t.beforeEach(async () => {
nock.disableNetConnect()
gitFixture.init()
})

t.test('close-pr should fail if config and dependent provided', async (t) => {
try {
childProcess.execSync(`${wibyCommand} close-pr --config=.wiby.json --dependent="https://github.com/wiby-test/fakeRepo"`)
tap.fail()
} catch (err) {
tap.equal(true, err.message.includes('Arguments dependent and config are mutually exclusive'))
}
})
t.test('close-pr should call and close the PR on command with dependent argument', async (t) => {
const result = childProcess.execSync(`${wibyCommand} close-pr --dependent="https://github.com/wiby-test/fakeRepo"`, {
env: {
...process.env,
NODE_OPTIONS: `-r ${fixturesPath}/http/close-pr-command-positive.js`
}
}).toString()
t.match(result, '1 PRs closed\n')
})
t.test('close-pr should call and close the PR on command using wiby.json settings', async (t) => {
const result = childProcess.execSync(`${wibyCommand} close-pr`, {
env: {
...process.env,
NODE_OPTIONS: `-r ${fixturesPath}/http/close-pr-command-positive.js`
}
}).toString()
t.match(result, '1 PRs closed\n')
})
})
Loading

0 comments on commit b0fb895

Please sign in to comment.