Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Apply some simple checks on each PR.

## Use Cases

We have 4 functions available:
We have 5 functions available:

- **specification**: It validates GitHub Pull Requests against some specifications:
- `body` should be at least 8 characters long
Expand All @@ -25,6 +25,10 @@ We have 4 functions available:
- if no fixup commits are found, PR is validated
- **autoMerge**: It merges PR from @dependabot when the update it for a patch or a minor version
- the repo must have the _auto merge_ option enabled
- **artifacts**: It checks if `package.json` dependencies contain artifact URLs matching a configured regex
- it checks `package.json` files at the repository root or in subfolders
- if a matching dependency is found, PR is blocked
- if no matching dependency is found, PR is validated

## Prerequisites

Expand Down Expand Up @@ -62,6 +66,7 @@ You can update some options from the `serverless.yml` file:
- `CHECK_BODY_LENGTH`: change the minimun length of the body of the PR
- `CHECK_TITLE_LENGTH`: change the minimun length of the title of the PR
- `BLOCK_LABELS`: define which label will block a PR (comma separated strings)
- `ARTIFACTS_REGEX`: define which artifact URLs in `package.json` dependencies will block a PR

## Info

Expand Down
80 changes: 43 additions & 37 deletions functions/classes/ArtifactsHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,53 +36,59 @@ export class ArtifactsHandler extends Handler {
pull_number: body.pull_request.number,
})

// loop through all files to see if package.json has been updated
const file = files.data.find(({ filename }) => filename.includes('package.json'))
// loop through all files to see if package.json has been updated (root or subfolders)
const packageJsonFiles = files.data.filter(
({ filename }) => filename === 'package.json' || filename.endsWith('/package.json')
)

if (!file) {
if (packageJsonFiles.length === 0) {
console.info('No package.json found in PR')

response = await this.updateStatus(body, payload.success)

return response
}

const refMatch = file.contents_url.match(/ref=([a-z0-9]+)/)
if (!refMatch?.[1]) {
console.warn('no ref in package.json diff url?')

response = await this.updateStatus(body, payload.success)

return response
}

const content = await this.githubClient.rest.repos.getContent({
owner: body.repository.owner.login,
repo: body.repository.name,
path: file.filename,
ref: refMatch[1],
})

let packageJson = ''
try {
packageJson = JSON.parse(
Buffer.from(content.data.content.toString('utf8'), 'base64').toString('ascii')
)
} catch (e) {
response = await this.updateStatus(body, payload.success)
console.error('Parsing package.json failed:', e)

return response
}

const deps = {
dependencies: packageJson.dependencies || {},
devDependencies: packageJson.devDependencies || {},
let artifactFound = false

for (const file of packageJsonFiles) {
const refMatch = file.contents_url.match(/ref=([a-z0-9]+)/)
if (!refMatch?.[1]) {
console.warn(`no ref in ${file.filename} diff url?`)
continue
}

const content = await this.githubClient.rest.repos.getContent({
owner: body.repository.owner.login,
repo: body.repository.name,
path: file.filename,
ref: refMatch[1],
})

let packageJson = ''
try {
packageJson = JSON.parse(
Buffer.from(content.data.content.toString('utf8'), 'base64').toString('ascii')
)
} catch (e) {
console.error(`Parsing ${file.filename} failed:`, e)
continue
}

const deps = {
dependencies: packageJson.dependencies || {},
devDependencies: packageJson.devDependencies || {},
}

const match = JSON.stringify(deps).match(this.artifactsRegex)

if (match !== null) {
artifactFound = true
break
}
}

const match = JSON.stringify(deps).match(this.artifactsRegex)

if (match === null) {
if (!artifactFound) {
console.info('No match, success.')

response = await this.updateStatus(body, payload.success)
Expand Down
78 changes: 78 additions & 0 deletions tests/artifacts.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,84 @@ describe('Artifacts check', () => {
)
})

test('got a package.json file in subfolder and block merge', async () => {
fetchMock
.mockGlobal()
.route('https://api.github.com/repos/foo/bar/pulls/42/files', [
{
sha: 'c14ebcf2a1782dec1bf65b2d4fbc295964b81754',
filename: 'main/package.json',
status: 'modified',
additions: 1,
deletions: 1,
changes: 2,
blob_url:
'https://github.com/foo/bar/blob/d3f0969ba9b5d5f5713fd804246886917adab874/main/package.json',
raw_url:
'https://github.com/foo/bar/raw/d3f0969ba9b5d5f5713fd804246886917adab874/main/package.json',
contents_url:
'https://api.github.com/repos/foo/bar/contents/main/package.json?ref=d3f0969ba9b5d5f5713fd804246886917adab874',
patch:
'@@ -1,5 +1,5 @@\n "dependencies": {\n- "super": "^1.0.0"\n+ "super": "http://localhost:1234/package.zip"\n }',
},
])
.route(
'https://api.github.com/repos/foo/bar/contents/main%2Fpackage.json?ref=d3f0969ba9b5d5f5713fd804246886917adab874',
{
name: 'package.json',
path: 'main/package.json',
sha: 'c366d3955407f49b5110aae7d8d04fcf0b3ef1de',
size: 86,
type: 'file',
content:
'ewogICJkZXBlbmRlbmNpZXMiOiB7CiAgICAic3VwZXIiOiAiaHR0cDovL2xvY2FsaG9zdDoxMjM0L3BhY2thZ2UuemlwIgogIH0KfQ==',
encoding: 'base64',
}
)
.route(
'https://api.github.com/repos/foo/bar/statuses/ee55a1223ce20c3e7cb776349cb7f8efb7b88511',
200
)
const githubEvent = {
pull_request: {
number: 42,
title: 'Update',
body: 'This is a pretty simple change that we need to pull into master.',
head: {
sha: 'ee55a1223ce20c3e7cb776349cb7f8efb7b88511',
},
base: {
sha: '1e55a1223ce20c3e7cb776349cb7f8efb7b8851e',
},
},
repository: {
name: 'bar',
full_name: 'foo/bar',
owner: {
login: 'foo',
},
},
}

const fixup = new ArtifactsHandler('GH_TOKEN', 'THE BRAND', '(localhost:)')
const response = await fixup.handle(githubEvent)
expect(response).toEqual({
body: 'Process finished with state: failure',
statusCode: 204,
})

expect(fetch).toHaveLastFetched(
'https://api.github.com/repos/foo/bar/statuses/ee55a1223ce20c3e7cb776349cb7f8efb7b88511',
{
body: {
state: 'failure',
description: 'Artifacts found in deps, remove them to merge',
context: 'THE BRAND - Artifacts check',
},
}
)
})

test('got a package.json file and block merge', async () => {
fetchMock
.mockGlobal()
Expand Down