Skip to content

Commit

Permalink
Initial proof of concept for jest-runtime based Dangerfile parsing (#54)
Browse files Browse the repository at this point in the history
  • Loading branch information
orta authored and macklinu committed Dec 21, 2016
1 parent e743775 commit 0ba01b1
Show file tree
Hide file tree
Showing 26 changed files with 533 additions and 137 deletions.
1 change: 1 addition & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
distribution
.*/_tests/.*
.*dangerfile.js
node_modules/jest*

[include]

Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ jspm_packages
.node_repl_history

distribution
flow-typed
flow-typed
env/development.env
23 changes: 22 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
{
"version": "0.2.0",
"configurations": [{
"name": "Launch",
"name": "Launch Run",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/source/commands/danger-run.js",
"stopOnEntry": false,
"args": [],
"envFile": "${workspaceRoot}/env/development.env",
"cwd": "${workspaceRoot}",
"preLaunchTask": "build",
"runtimeExecutable": null,
"runtimeArgs": [
"--nolazy"
],
"env": {
"NODE_ENV": "development"
},
"console": "internalConsole",
"sourceMaps": true,
"outDir": "${workspaceRoot}/distribution"
},
{
"name": "Launch Local",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/source/commands/danger-local.js",
"stopOnEntry": false,
"args": [],
"cwd": "${workspaceRoot}",
"preLaunchTask": "build",
"runtimeExecutable": null,
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
},
"files.associations": {
"*.js": "javascriptreact"
}
}
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ npm install -g flow-typed
flow-typed install
```

( and maybe `flow-typed install jest@14`)
( and maybe `flow-typed install jest`)

Tips:

Expand Down
6 changes: 6 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

// Add your own contribution below

* You can build and run in vscode using your own custom `env/development.env` file. This is useful because you can use the debugger against a real PR. See `env/development.env.example` for syntax. - orta

* Uses `jest-transform` and `jest-runtime` to eval and apply babel transforms.
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


### 0.6.10

* Brings back the ability to emulate a fake CI run locally via `danger` - orta
Expand Down
5 changes: 3 additions & 2 deletions dangerfile.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// @flow

import { danger, warn } from "danger"
const fs = require("fs")
// import { danger, warn } from "danger"
import fs from "fs"
console.log("Hello world")

// Request a CHANGELOG entry
const hasChangelog = danger.git.modified_files.includes("changelog.md")
Expand Down
4 changes: 4 additions & 0 deletions env/development.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DANGER_FAKE_CI="sure"
DANGER_TEST_REPO="artsy/emission"
DANGER_TEST_PR="327"
DANGER_GITHUB_API_TOKEN="123456789123456789123456789"
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@
"eslint-plugin-flowtype": "^2.25.0",
"eslint-plugin-promise": "^3.4.0",
"eslint-plugin-standard": "^2.0.0",
"jest": "^18.0.0",
"flow-bin": "^0.35.0",
"in-publish": "^2.0.0",
"jest-cli": "^18.0.0"
"in-publish": "^2.0.0"
},
"dependencies": {
"babel-polyfill": "^6.16.0",
Expand Down
1 change: 0 additions & 1 deletion source/ci_source/ci_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,3 @@ export function getCISourceForEnv(env: Env): ?CISource {
return fake
}
}

2 changes: 0 additions & 2 deletions source/commands/danger-local.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,3 @@ var program = require("commander")

program
.parse(process.argv)

console.log("Not Yet implmented")
14 changes: 13 additions & 1 deletion source/dsl/GitHubDSL.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,19 @@ export interface GitHubRepo {
* Is the repo a fork?
* @type {bool}
*/
fork: false
fork: bool,

/**
* IS someone assigned to this PR?
* @type {GitHubUser}
*/
assignee: GitHubUser,

/**
* Are there people assigned to this PR?
* @type {Array<GitHubUser>}
*/
assignees: Array<GitHubUser>,
}

export interface GitHubMergeRef {
Expand Down
1 change: 1 addition & 0 deletions source/platforms/_tests/platform.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ it("should bail if there is no DANGER_GITHUB_API_TOKEN found", () => {
getPlatformForEnv({}, {})
}).toThrow("Cannot use authenticated API requests")
})

132 changes: 39 additions & 93 deletions source/runner/Dangerfile.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
// @flow

// https://nodejs.org/api/vm.html
// https://60devs.com/executing-js-code-with-nodes-vm-module.html

import fs from "fs"
import vm from "vm"
import type { DangerResults } from "./DangerResults"
import type { DangerDSLType } from "../dsl/DangerDSL"
import type { MarkdownString } from "../dsl/Aliases"

interface DangerContext {
export interface DangerContext {
/* BEGIN FLOWTYPE EXPORT */
/**
* Fails a build, outputting a specific reason for failing
Expand Down Expand Up @@ -42,107 +37,58 @@ interface DangerContext {
/** Typical console */
console: any;

/** Typical require statement */
require(id: string): any;

/**
* The Danger object to work with
*
* @type {DangerDSLType}
*/
danger: DangerDSLType
danger: DangerDSLType;
/**
* Results of a Danger run
*
* @type {DangerDSLType}
*/
results: DangerResults;
/* END FLOWTYPE EXPORT */
}

export class Dangerfile {
dsl: DangerDSLType

constructor(dsl: DangerDSLType) {
this.dsl = dsl
/** Creates a Danger context, this provides all of the global functions
* which are available to the Danger eval runtime.
*
* @param {DangerDSLType} dsl The DSL which is turned into `danger`
* @returns {DangerContext} a DangerContext-like API
*/
export function contextForDanger(dsl: DangerDSLType): DangerContext {
const results: DangerResults = {
fails: [],
warnings: [],
messages: [],
markdowns: []
}

async run(file: string): Promise<DangerResults> {
const contents = await this.readFile(file)

// comment out imports of 'danger'
// e.g `import danger from`
// then user get typed data, and we fill it in
// via the VM context

const cleaned = contents
.replace(/import danger /gi, "// import danger ")
.replace(/import { danger/gi, "// import { danger ")

const script = new vm.Script(cleaned, {
filename: file,
lineOffset: 1,
columnOffset: 1,
displayErrors: true,
timeout: 1000 // ms
})

const results: DangerResults = {
fails: [],
warnings: [],
messages: [],
markdowns: []
}

const fail = (message: MarkdownString) => {
results.fails.push({ message })
}

const warn = (message: MarkdownString) => {
results.warnings.push({ message })
}

const message = (message: MarkdownString) => {
results.messages.push({ message })
}

const markdown = (message: MarkdownString) => {
results.markdowns.push(message)
}
const fail = (message: MarkdownString) => {
results.fails.push({ message })
}

const context: DangerContext = {
fail,
warn,
message,
markdown,
console,
require,
danger: this.dsl
}
const warn = (message: MarkdownString) => {
results.warnings.push({ message })
}

try {
script.runInNewContext(context)
}
catch (e) {
console.log(e.toString())
}
const message = (message: MarkdownString) => {
results.messages.push({ message })
}

return results
const markdown = (message: MarkdownString) => {
results.markdowns.push(message)
}

/**
* A dumb fs.readFile promise wrapper,
* converts to string
*
* @param {string} path filepath
* @returns {Promise<string>} probably your string
*/
readFile(path: string): Promise<string> {
return new Promise((resolve: any, reject: any) => {
fs.readFile(path, (err: any, data: Buffer) => {
if (err) {
console.error("Error: " + err.message)
process.exitCode = 1
reject(err)
} else {
resolve(data.toString())
}
})
})
return {
fail,
warn,
message,
markdown,
console,
results,
danger: dsl
}
}

0 comments on commit 0ba01b1

Please sign in to comment.