Skip to content

Commit

Permalink
refactor(esm): use esm & refactor tests with more details
Browse files Browse the repository at this point in the history
  • Loading branch information
Ahmad Nassri committed Aug 25, 2020
1 parent 1538a97 commit 40fdd76
Show file tree
Hide file tree
Showing 18 changed files with 219 additions and 190 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,5 @@ steps:
| -------------- | -------- | -------------- | --------------------------------------------------- |
| `target` | ❌ | `patch` | The version comparison target (major, minor, patch) |
| `github-token` | ❌ | `github.token` | The GitHub token used to merge the pull-request |
| `command` | ❌ | `merge` | The command to pass to Dependabot |
| `command` | ❌ | `merge` | The command to pass to Dependabot |
| `approve` | ❌ | `true` | Auto-approve pull-requests |
8 changes: 8 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ inputs:
description: The GitHub token used to merge the pull-request
default: ${{ github.token }}

command:
description: The command to pass to Dependabot as a comment
default: merge

approve:
description: Auto-approve pull-requests
default: true

target:
description: The version comparison target (major, minor, patch)
default: patch
Expand Down
46 changes: 8 additions & 38 deletions action/index.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,25 @@
// packages
const core = require('@actions/core')
const github = require('@actions/github')
import core from '@actions/core'

// modules
const parse = require('./lib/parse')
const approve = require('./lib/approve')
const comment = require('./lib/comment')
import main from './lib/index.js'

// parse inputs
const inputs = {
token: core.getInput('github-token', { required: true }),
target: core.getInput('target', { required: true })
target: core.getInput('target', { required: true }),
command: core.getInput('command', { required: false }),
approve: core.getInput('approve', { required: false })
}

// error handler
function errorHandler (err) {
core.error(`Unhandled error: ${err}`)
function errorHandler ({ message, stack }) {
core.error(`${message}\n${stack}`)
process.exit(1)
}

// catch errors and exit
process.on('unhandledRejection', errorHandler)
process.on('uncaughtException', errorHandler)

// exit early
if (github.context.eventName !== 'pull_request') {
core.error('action triggered outside of a pull_request')
process.exit(1)
}

// extract the title
const { repo, payload: { sender, pull_request } } = github.context // eslint-disable-line camelcase

// exit early if PR is not by dependabot
if (sender.login !== 'dependabot[bot]') {
core.warning(`expected PR by "dependabot[bot]", found "${sender.login}" instead`)
process.exit(0)
}

// init octokit
const octokit = github.getOctokit(inputs.token)

async function main () {
// parse and determine what command to tell dependabot
const command = parse(pull_request.title, inputs.target || 'patch')

if (command === 'merge') {
await approve(octokit, repo, pull_request)
await comment(octokit, repo, pull_request, `@dependabot ${command}`)
}
}

// awaiting top-level await
main()
await main(inputs)
21 changes: 21 additions & 0 deletions action/lib/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export async function approve (octokit, repo, { number }) {
const { data: { id } } = await octokit.pulls.createReview({
...repo,
pull_number: number
})

await octokit.pulls.submitReview({
...repo,
pull_number: number,
event: 'APPROVE',
review_id: id
})
}

export async function comment (octokit, repo, { number }, body) { // eslint-disable-line camelcase
await octokit.issues.createComment({
...repo,
issue_number: number,
body
})
}
13 changes: 0 additions & 13 deletions action/lib/approve.js

This file was deleted.

7 changes: 0 additions & 7 deletions action/lib/comment.js

This file was deleted.

35 changes: 35 additions & 0 deletions action/lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import core from '@actions/core'
import github from '@actions/github'

// modules
import parse from './parse.js'
import { approve, comment } from './api.js'

export default async function (inputs) {
// exit early
if (github.context.eventName !== 'pull_request') {
core.error('action triggered outside of a pull_request')
return process.exit(1)
}

// extract the title
const { repo, payload: { sender, pull_request } } = github.context // eslint-disable-line camelcase

// exit early if PR is not by dependabot
if (!sender || !['dependabot[bot]', 'dependabot-preview[bot]'].includes(sender.login)) {
core.warning(`expected PR by "dependabot[bot]", found "${sender.login}" instead`)
return process.exit(0)
}

// init octokit
const octokit = github.getOctokit(inputs.token)

// parse and determine what command to tell dependabot
const proceed = parse(pull_request.title, inputs.target || 'patch')

if (proceed) {
if (inputs.approve) await approve(octokit, repo, pull_request)

await comment(octokit, repo, pull_request, `@dependabot ${inputs.command}`)
}
}
16 changes: 8 additions & 8 deletions action/lib/parse.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
const { diff, valid } = require('semver')
const core = require('@actions/core')
import semver from 'semver'
import core from '@actions/core'

// semver regex
const semver = /(?<version>(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<patch>0|[1-9]\d*)(?:-(?<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)/
const semverRegEx = /(?<version>(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<patch>0|[1-9]\d*)(?:-(?<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)/

const weight = {
major: 3,
minor: 2,
patch: 1
}

module.exports = function (title, target) {
export default function (title, target) {
// log
core.info(`title: "${title}"`)

// extract version from the title
const from = title.match(new RegExp('from ' + semver.source))?.groups
const to = title.match(new RegExp('to ' + semver.source))?.groups
const from = title.match(new RegExp('from ' + semverRegEx.source))?.groups
const to = title.match(new RegExp('to ' + semverRegEx.source))?.groups

// exit early
if (!from || !to || !valid(from.version) || !valid(to.version)) {
if (!from || !to || !semver.valid(from.version) || !semver.valid(to.version)) {
core.error('failed to parse title: invalid semver')
return process.exit(0) // soft exit
}
Expand All @@ -29,7 +29,7 @@ module.exports = function (title, target) {
core.info(`to: ${to.version}`)

// analyze with semver
const result = diff(from.version, to.version)
const result = semver.diff(from.version, to.version)

// compare weight to target
if ((weight[target] || 0) >= (weight[result] || 0)) {
Expand Down
7 changes: 4 additions & 3 deletions action/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
"email": "[email protected]",
"url": "https://ahmadnassri.com"
},
"type": "module",
"main": "index.js",
"license": "MIT",
"scripts": {
"test": "tap test --no-coverage",
"test:watch": "tap --watch",
"test:100": "tap test --100 --color --coverage-report=lcov --no-browser"
"test": "tap --no-esm --no-coverage",
"test:watch": "tap --no-esm --watch",
"test:100": "tap --no-esm --100 --color --coverage-report=lcov --no-browser"
},
"dependencies": {
"@actions/core": "^1.2.4",
Expand Down
24 changes: 0 additions & 24 deletions action/test/in-range.js

This file was deleted.

52 changes: 52 additions & 0 deletions action/test/main/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// packages
import tap from 'tap'
import sinon from 'sinon'
import core from '@actions/core'
import github from '@actions/github'

// module
import main from '../../lib/index.js'

tap.test('main -> wrong event', assert => {
assert.plan(3)

sinon.stub(core, 'error')
sinon.stub(process, 'exit')

sinon.stub(github, 'context').value({
eventName: 'not-a-pull_request'
})

main()

assert.ok(process.exit.called)
assert.equal(process.exit.getCall(0).args[0], 1)
assert.equal(core.error.getCall(0).args[0], 'action triggered outside of a pull_request')

process.exit.restore()
core.error.restore()
})

tap.test('main -> not dependabot', assert => {
assert.plan(3)

sinon.stub(core, 'warning')
sinon.stub(process, 'exit')

sinon.stub(github, 'context').value({
eventName: 'pull_request',
repo: 'foo/bar',
payload: {
sender: { login: 'foo' }
}
})

main()

assert.ok(process.exit.called)
assert.equal(process.exit.getCall(0).args[0], 0)
assert.equal(core.warning.getCall(0).args[0], 'expected PR by "dependabot[bot]", found "foo" instead')

process.exit.restore()
core.warning.restore()
})
23 changes: 0 additions & 23 deletions action/test/no-merge.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// packages
const { test } = require('tap')
const sinon = require('sinon')
const core = require('@actions/core')
import tap from 'tap'
import sinon from 'sinon'
import core from '@actions/core'

// module
const parse = require('../lib/parse')
import parse from '../../lib/parse.js'

test('title -> fail', assert => {
tap.test('parse -> invalid semver', assert => {
assert.plan(3)

sinon.stub(core, 'info') // silence output on terminal
Expand Down
40 changes: 40 additions & 0 deletions action/test/parse/match-target.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// packages
import tap from 'tap'
import sinon from 'sinon'
import core from '@actions/core'

// module
import parse from '../../lib/parse.js'

tap.test('title -> in range', async assert => {
assert.plan(6)

sinon.stub(core, 'info')

const proceed = parse('chore(deps): bump api-problem from 6.1.2 to 6.1.4 in /path', 'major')

assert.ok(proceed)
assert.ok(core.info.called)
assert.equal(core.info.getCall(0).args[0], 'title: "chore(deps): bump api-problem from 6.1.2 to 6.1.4 in /path"')
assert.equal(core.info.getCall(1).args[0], 'from: 6.1.2')
assert.equal(core.info.getCall(2).args[0], 'to: 6.1.4')
assert.equal(core.info.getCall(3).args[0], 'dependency update target is "major", found "patch", will auto-merge')

core.info.restore()
})

tap.test('parse -> out of range', async assert => {
assert.plan(5)

sinon.stub(core, 'info')

const proceed = parse('chore(deps): bump api-problem from 6.1.2 to 7.0.0 in /path', 'patch')

assert.notOk(proceed, false)
assert.ok(core.info.called)
assert.equal(core.info.getCall(1).args[0], 'from: 6.1.2')
assert.equal(core.info.getCall(2).args[0], 'to: 7.0.0')
assert.equal(core.info.getCall(3).args[0], 'manual merging required')

core.info.restore()
})
Loading

0 comments on commit 40fdd76

Please sign in to comment.