Skip to content

Commit

Permalink
Merge pull request #71 from kwonoj/feat-external-ci
Browse files Browse the repository at this point in the history
Allow to specify external CI source provider
  • Loading branch information
orta committed Dec 31, 2016
2 parents 44f0a54 + 31f6284 commit 2ec1f15
Show file tree
Hide file tree
Showing 21 changed files with 145 additions and 42 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"**/.svn": true,
"**/.hg": true,
"**/.DS_Store": true,
"distribution/": true
"distribution/": true,
"coverage": true
},
"search.exclude": {
"**/node_modules": true,
Expand Down
3 changes: 2 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
### master

// Add your own contribution below
* Support `danger run -ci` to specify external CI provider -kwonoj

### 0.7.4

Expand Down Expand Up @@ -33,7 +34,7 @@

This does two things, makes it feasible to do [hosted-danger](https://github.com/danger/peril) and
makes it possible to write your Dangerfile in a way that's consistent with the rest of your JavaScript. - orta

* Add tests directory to .npmignore - macklinu
* Update to Jest 18 - macklinu

Expand Down
35 changes: 35 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
type Env = any;
declare var CISource: Danger.CISource

declare namespace Danger {
export interface CISourceConstructor {
new (env?: Env): CISource;
}

export interface CISource implements CISourceConstructor {
/** The project name, mainly for showing errors */
readonly name: string,

/** The hash of environment variables */
readonly env: Env,

/** Does this validate as being on a particular CI? */
readonly isCI: boolean,

/** Does this validate as being on a particular PR on a CI? */
readonly isPR: boolean,

/** What is the reference slug for this environment? */
readonly repoSlug: string,

/** What platforms can this CI communicate with? */
readonly supportedPlatforms: Array<string>,

/** What unique id can be found for the code review platform's PR */
readonly pullRequestID: string,
}
}

declare module "danger" {
export = Danger;
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.7.3",
"description": "Unit tests for Team Culture",
"main": "distribution/danger.js",
"typings": "./index.d.ts",
"bin": {
"danger": "distribution/commands/danger.js"
},
Expand Down
17 changes: 15 additions & 2 deletions source/ci_source/_tests/_ci_sourse.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// @flow

import Fake from "../Fake"
import { getCISourceForEnv } from "../ci_source"
import Fake from "../providers/Fake"
import DummyCI from "./fixtures/dummy_ci"
import { getCISourceForEnv, getCISourceForExternal } from "../ci_source"

describe(".getCISourceForEnv", () => {
test("returns undefined if nothing is found", () => {
Expand All @@ -14,3 +15,15 @@ describe(".getCISourceForEnv", () => {
expect(ci).toBeInstanceOf(Fake)
})
})

describe(".getCISourceForExternal", () => {
test("should resolve module relatively", () => {
const ci = getCISourceForExternal({ }, "./source/ci_source/_tests/fixtures/dummy_ci.js")
expect(ci).toBeInstanceOf(DummyCI)
})

test("should return undefined if module resolution fails", () => {
const ci = getCISourceForExternal({ }, "./dummy_ci.js")
expect(ci).toBeUndefined()
})
})
13 changes: 13 additions & 0 deletions source/ci_source/_tests/fixtures/dummy_ci.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// @flow
"use strict"

export default class DummyCI {
get name(): string { return "Dummy Testing CI" }

get isCI(): boolean { return false }
get isPR(): boolean { return true }

get pullRequestID(): string { return this.env.pr }
get repoSlug(): string { return this.env.repo }
get supportedPlatforms(): string[] { return ["github"] }
}
73 changes: 50 additions & 23 deletions source/ci_source/ci_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,61 @@ export interface CISource {
+pullRequestID: string,
}

import Travis from "./Travis"
import Circle from "./Circle"
import Semaphore from "./Semaphore"
import Jenkins from "./Jenkins"
import Fake from "./Fake"

import providers from "./providers"
import fs from "fs"
import { resolve } from "path"
/**
* Gets a CI Source form the current environment, by asking all known
* Gets a CI Source from the current environment, by asking all known
* sources if they can be represented in this environment.
* @param {Env} env The environment.
* @returns {?CISource} a CI source if it's OK, otherwise Danger can't run.
*/
export function getCISourceForEnv(env: Env): ?CISource {
const travis = new Travis(env)
const circle = new Circle(env)
const semaphore = new Semaphore(env)
const jenkins = new Jenkins(env)
const fake = new Fake(env)

if (travis.isCI) {
return travis
} else if (circle.isCI) {
return circle
} else if (semaphore.isCI) {
return semaphore
} else if (jenkins.isCI) {
return jenkins
} else if (fake.isCI) {
return fake
const availableProviders = [...providers]
.map(Provider => new Provider(env))
.filter(x => x.isCI)
return (availableProviders && availableProviders.length > 0) ? availableProviders[0] : undefined
}

/**
* Gets a CI Source from externally provided provider module.
* Module must implement CISource interface, and should export it as default
* @export
* @param {Env} env The environment.
* @param {string} modulePath relative path to CI provider
* @returns {?CISource} a CI source if module loaded successfully, undefined otherwise
*/
export function getCISourceForExternal(env: Env, modulePath: string): ?CISource {
const path = resolve(process.cwd(), modulePath)

try {
const exist = fs.statSync(path).isFile()

if (exist) {
// $FlowFixMe
const Ctor = require(path).default
return new Ctor()
}
} catch (e) {
console.error(`could not load CI provider at ${modulePath} due to ${e}`)
}
return undefined
}

/**
* Gets a CI Source.
* @export
* @param {Env} env The environment.
* @param {string} modulePath relative path to CI provider
* @returns {?CISource} a CI source if module loaded successfully, undefined otherwise
*/
export function getCISource(env: Env, modulePath: string): ?CISource {
if (modulePath) {
const external = getCISourceForExternal(env, modulePath)
if (external) {
return external
}
}

return getCISourceForEnv(env)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @flow
"use strict"

import type { Env } from "./ci_source"
import { ensureEnvKeysExist, ensureEnvKeysAreInt } from "./ci_source_helpers"
import type { Env } from "../ci_source"
import { ensureEnvKeysExist, ensureEnvKeysAreInt } from "../ci_source_helpers"

export default class Circle {
env: Env
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @flow
"use strict"

import type { Env } from "./ci_source"
import { ensureEnvKeysExist } from "./ci_source_helpers"
import type { Env } from "../ci_source"
import { ensureEnvKeysExist } from "../ci_source_helpers"

export default class FakeCI {
env: Env
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @flow
"use strict"

import type { Env } from "./ci_source"
import { ensureEnvKeysExist, ensureEnvKeysAreInt } from "./ci_source_helpers"
import type { Env } from "../ci_source"
import { ensureEnvKeysExist, ensureEnvKeysAreInt } from "../ci_source_helpers"

export default class Jenkins {
env: Env
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @flow
"use strict"

import type { Env } from "./ci_source"
import { ensureEnvKeysExist, ensureEnvKeysAreInt } from "./ci_source_helpers"
import type { Env } from "../ci_source"
import { ensureEnvKeysExist, ensureEnvKeysAreInt } from "../ci_source_helpers"

export default class Semaphore {
env: Env
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @flow
"use strict"

import type { Env } from "./ci_source"
import { ensureEnvKeysExist, ensureEnvKeysAreInt } from "./ci_source_helpers"
import type { Env } from "../ci_source"
import { ensureEnvKeysExist, ensureEnvKeysAreInt } from "../ci_source_helpers"

export default class Travis {
env: Env
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
10 changes: 10 additions & 0 deletions source/ci_source/providers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @flow

import Travis from "./Travis"
import Circle from "./Circle"
import Semaphore from "./Semaphore"
import Jenkins from "./Jenkins"
import Fake from "./Fake"

const providers: Array<any> = [Travis, Circle, Semaphore, Jenkins, Fake]
export default providers
6 changes: 4 additions & 2 deletions source/commands/danger-run.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ import "babel-polyfill"

var program = require("commander")

import { getCISourceForEnv } from "../ci_source/ci_source"
import { getCISource } from "../ci_source/ci_source"
import { getPlatformForEnv } from "../platforms/platform"
import Executor from "../runner/Executor"

program
.option("-f, --fail-on-errors", "TODO: Fail on errors")
.option("-c, --external-ci-provider [modulePath]", "blah")
.parse(process.argv)

process.on("unhandledRejection", function(reason: string, p: any) {
console.log("Error: ", reason)
process.exitCode = 1
})

const source = getCISourceForEnv(process.env)
const source = getCISource(process.env, program.externalCiProvider || undefined)

if (!source) {
console.log("Could not find a CI source for this run")
// Check for ENV["CI"] and wanr they might want a local command instead?
Expand Down
2 changes: 1 addition & 1 deletion source/platforms/_tests/GitHub.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/*eslint-disable */

import { GitHub } from "../GitHub"
import Fake from "../../ci_source/Fake"
import Fake from "../../ci_source/providers/Fake"
import { readFileSync } from "fs"
import { resolve } from "path"
import os from "os"
Expand Down
2 changes: 1 addition & 1 deletion source/runner/_tests/DangerRunner.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
updateDangerfile,
cleanDangerfile
} from "../DangerfileRunner"
import Fake from "../../ci_source/Fake"
import Fake from "../../ci_source/providers/Fake"
import FakePlatform from "../../platforms/FakePlatform"
import Executor from "../Executor"

Expand Down
2 changes: 1 addition & 1 deletion source/runner/_tests/Executor.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow

import Executor from "../Executor"
import Fake from "../../ci_source/Fake"
import Fake from "../../ci_source/providers/Fake"
import FakePlatform from "../../platforms/FakePlatform"
import { emptyResults, warnResults } from "./fixtures/ExampleDangerResults"

Expand Down

0 comments on commit 2ec1f15

Please sign in to comment.