Skip to content

Commit

Permalink
Merge pull request #436 from danger/init_2
Browse files Browse the repository at this point in the history
More work on danger init
  • Loading branch information
orta committed Dec 7, 2017
2 parents f3b741a + 67a578a commit e7ca5f2
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 86 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ developers, so please limit technical // terminology to in here.

// ### Master

### 2.1.1 - 2.1.2
### 2.1.1 - 2.1.2 - 2.1.3

* Fixes for `danger init` - there's always a patch :D - [@orta][]
* Fixes/Improvements for `danger init` - [@orta][]

### 2.1.0

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@
"node-cleanup": "^2.1.2",
"node-fetch": "^1.6.3",
"parse-diff": "^0.4.0",
"parse-git-config": "^1.1.1",
"parse-github-url": "^1.0.1",
"parse-link-header": "^1.0.1",
"pinpoint": "^1.1.0",
"readline-sync": "^1.4.7",
Expand Down
3 changes: 3 additions & 0 deletions source/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ declare module "supports-hyperlinks"
// */
// export default function(code: string, filename?: string, opts?: Partial<RequireOptions>): any
// }

declare module "parse-git-config"
declare module "parse-github-url"
98 changes: 17 additions & 81 deletions source/commands/danger-init.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import * as chalk from "chalk"
import * as hyperLinker from "hyperlinker"
import * as program from "commander"
import * as readlineSync from "readline-sync"
import * as supportsHyperlinks from "supports-hyperlinks"

import * as fs from "fs"
import { basename } from "path"
import { setTimeout } from "timers"

import { generateDefaultDangerfile } from "./init/default-dangerfile"
import { travis, circle, unsure } from "./init/add-to-ci"
import { generateInitialState, createUI } from "./init/state-setup"
import { InitUI, InitState, highlight } from "./init/interfaces"

program
.description("Helps you get set up through to your first Danger.")
Expand All @@ -20,80 +18,6 @@ interface App {

const app: App = program as any

interface InitUI {
header: (msg: String) => void
command: (command: string) => void
say: (msg: String) => void
pause: (secs: number) => Promise<{}>
waitForReturn: () => void
link: (name: string, href: string) => string
askWithAnswers: (message: string, answers: string[]) => string
}

export interface InitState {
filename: string
botName: string

isWindows: boolean
isMac: boolean
isBabel: boolean
isTypeScript: boolean
supportsHLinks: boolean

isAnOSSRepo: boolean

hasCreatedDangerfile: boolean
hasSetUpAccount: boolean
hasSetUpAccountToken: boolean
}
const createUI = (state: InitState, app: App): InitUI => {
const say = (msg: String) => console.log(msg)
const fancyLink = (name: string, href: string) => hyperLinker(name, href)
const inlineLink = (_name: string, href: string) => chalk.underline(href)
const linkToUse = state.supportsHLinks ? fancyLink : inlineLink

return {
say,
header: (msg: String) => say(chalk.bold("\n## " + msg + "\n")),
command: (command: string) => say("> " + chalk.white.bold(command) + " \n"),
link: (name: string, href: string) => linkToUse(name, href),
pause: async (secs: number) => new Promise(done => setTimeout(done, secs * 1000)),
waitForReturn: () => (app.impatient ? Promise.resolve() : readlineSync.question("\n↵ ")),
askWithAnswers: (_message: string, answers: string[]) => {
const a = readlineSync.keyInSelect(answers, "", { defaultInput: answers[0] })
return answers[a]
},
}
}

const checkForTypeScript = () => fs.existsSync("node_modules/typescript/package.json")
const checkForBabel = () =>
fs.existsSync("node_modules/babel-core/package.json") || fs.existsSync("node_modules/@babel/core/package.json")

const capitalizeFirstLetter = (string: string) => string.charAt(0).toUpperCase() + string.slice(1)
const camelCase = (str: string) => str.split("-").reduce((a, b) => a + b.charAt(0).toUpperCase() + b.slice(1))

const generateInitialState = (osProcess: NodeJS.Process): InitState => {
const isMac = osProcess.platform === "darwin"
const isWindows = osProcess.platform === "win32"
const folderName = capitalizeFirstLetter(camelCase(basename(osProcess.cwd())))
const isTypeScript = checkForTypeScript()
const isBabel = checkForBabel()
return {
isMac,
isWindows,
isTypeScript,
isBabel,
isAnOSSRepo: true,
supportsHLinks: supportsHyperlinks.stdout,
filename: isTypeScript ? "Dangerfile.ts" : "Dangerfile.js",
botName: folderName + "Bot",
hasSetUpAccount: false,
hasCreatedDangerfile: false,
hasSetUpAccountToken: false,
}
}

const go = async (app: App) => {
const state = generateInitialState(process)
const ui: InitUI = createUI(state, app)
Expand All @@ -104,12 +28,11 @@ const go = async (app: App) => {
await setupDangerfile(ui, state)
await setupGitHubAccount(ui, state)
await setupGHAccessToken(ui, state)
await addToCI(ui, state)
await wrapItUp(ui, state)
await thanks(ui, state)
}

const highlight = chalk.bold.yellow

const showTodoState = async (ui: InitUI) => {
ui.say("Welcome to Danger Init - this will take you through setting up Danger for this project.")
ui.say("There are four main steps we need to do:\n")
Expand Down Expand Up @@ -276,6 +199,19 @@ const wrapItUp = async (ui: InitUI, _state: InitState) => {
await ui.pause(1)
}

const addToCI = async (ui: InitUI, state: InitState) => {
ui.header("Add to CI")

await ui.pause(0.6)
if (state.ciType === "travis") {
await travis(ui, state)
} else if (state.ciType === "circle") {
await circle(ui, state)
} else {
await unsure(ui, state)
}
}

const thanks = async (ui: InitUI, _state: InitState) => {
ui.say("\n\n🎉\n")
await ui.pause(0.6)
Expand Down
101 changes: 101 additions & 0 deletions source/commands/init/add-to-ci.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { InitUI, InitState, highlight } from "./interfaces"

export const travis = async (ui: InitUI, state: InitState) => {
// https://travis-ci.org/artsy/eigen/settings

if (state.repoSlug) {
const travisLink = ui.link(
`Travis Settings for ${state.repoSlug}`,
`https://travis-ci.org/${state.repoSlug}/settings`
)
ui.say("In order to add the environment variable, go to: " + travisLink)
} else {
const travisLink = ui.link("Travis Account profile", "https://travis-ci.org/profile/")
ui.say("In order to add the environment variable, go to: " + travisLink)
ui.say("And find the project for this repo, click the settings cog.")
}

ui.say(
"The variable name is " +
highlight("DANGER_GITHUB_API_TOKEN") +
" and the value is the GitHub Personal Access Token you just created."
)
if (state.isAnOSSRepo) {
ui.say('As you have an OSS repo - make sure to have "Display value in build log" enabled.')
}

ui.say("Next, you need to edit your `.travis.yml` to include `yarn danger`. If you already have")
ui.say("a `script:` section then we recommend adding this command at the end of the script step: `- yarn danger`.\n")
ui.say("Otherwise, add a `before_script` step to the root of the `.travis.yml` with `yarn danger`\n")

ui.say(" ```yaml")
ui.say(" before_script:")
ui.say(" - yarn danger")
ui.say(" ```\n")

ui.say("Adding this to your `.travis.yml` allows Danger to fail your build.")
ui.say("With that set up, you can edit your job to add `yarn danger` at the build action.")
}

export const circle = async (ui: InitUI, state: InitState) => {
// https://circleci.com/gh/artsy/eigen/edit#env-vars
const repo = state.repoSlug || "[Your_Repo]"
if (state.isAnOSSRepo) {
ui.say(
"Before we start, it's important to be up-front. CircleCI only really has one option to support running Danger"
)
ui.say(
"for forks on OSS repos. It is quite a drastic option, and I want to let you know the best place to understand"
)
ui.say("the ramifications of turning on a setting I'm about to advise.\n")
ui.link("Circle CI: Fork PR builds", "https://circleci.com/docs/fork-pr-builds")
ui.say(
"TLDR: If you have anything other than Danger config settings in CircleCI, then you should not turn on this setting."
)
ui.say("I'll give you a minute to read it...")
ui.waitForReturn()

ui.say(
"On danger/danger we turn on " +
highlight("Permissive building of fork pull requests") +
" this exposes the token to Danger"
)
const circleSettings = ui.link("Circle Settings", `https://circleci.com/gh/${repo}/edit#advanced-settings`)
ui.say(`You can find this setting at: ${circleSettings}.`)
ui.say("I'll hold...")
ui.waitForReturn()
}

const circleSettings = ui.link("Circle Env Settings", `https://circleci.com/gh/${repo}/edit#env-vars`)
ui.say(`In order to expose the environment variable, go to: ${circleSettings}`)

ui.say("The name is " + highlight("DANGER_GITHUB_API_TOKEN") + " and the value is the GitHub Personal Access Token.")

ui.say("With that set up, you can you add `yarn danger` to your `circle.yml`. If you override the default")
ui.say("`test:` section, then add danger as an extra step. \nOtherwise add a new `pre` section to the test:\n")
ui.say(" ``` ruby")
ui.say(" test:")
ui.say(" override:")
ui.say(" - yarn danger")
ui.say(" ```")
}

export const unsure = async (ui: InitUI, _state: InitState) => {
ui.say(
"You need to expose a token called " +
highlight("DANGER_GITHUB_API_TOKEN") +
" and the value is the GitHub Personal Access Token."
)
ui.say(
"Depending on the CI system, this may need to be done on the machine (in the " +
highlight("~/.bashprofile") +
") or in a web UI somewhere."
)
ui.say("We have a guide for all supported CI systems on danger.systems:")
ui.say(
ui.link(
"Danger Systems - Getting Started",
"http://danger.systems/js/guides/getting_started.html#setting-up-danger-to-run-on-your-ci"
)
)
}
3 changes: 1 addition & 2 deletions source/commands/init/default-dangerfile.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as fs from "fs"

import { InitState } from "../danger-init"
import { InitState } from "./interfaces"

const generateDangerfileState = () => ({
hasCHANGELOG: fs.existsSync("CHANGELOG.md"),
Expand Down
13 changes: 13 additions & 0 deletions source/commands/init/get-repo-slug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as parseGitConfig from "parse-git-config"
import * as parseGithubURL from "parse-github-url"

export const getRepoSlug = () => {
const config = parseGitConfig.sync()
const possibleRemotes = [config['remote "upstream"'], config['remote "origin"']].filter(f => f)
if (possibleRemotes.length === 0) {
return null
}

const ghData = possibleRemotes.map(r => parseGithubURL(r.url))[0]
return ghData.repo
}
33 changes: 33 additions & 0 deletions source/commands/init/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as chalk from "chalk"

export interface InitState {
filename: string
botName: string

isWindows: boolean
isMac: boolean
isBabel: boolean
isTypeScript: boolean
supportsHLinks: boolean

isAnOSSRepo: boolean

hasCreatedDangerfile: boolean
hasSetUpAccount: boolean
hasSetUpAccountToken: boolean

repoSlug: string | null
ciType: "travis" | "circle" | "unknown"
}

export interface InitUI {
header: (msg: String) => void
command: (command: string) => void
say: (msg: String) => void
pause: (secs: number) => Promise<{}>
waitForReturn: () => void
link: (name: string, href: string) => string
askWithAnswers: (message: string, answers: string[]) => string
}

export const highlight = chalk.bold.yellow
65 changes: 65 additions & 0 deletions source/commands/init/state-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as readlineSync from "readline-sync"
import * as supportsHyperlinks from "supports-hyperlinks"
import * as hyperLinker from "hyperlinker"
import * as chalk from "chalk"

import { basename } from "path"
import { setTimeout } from "timers"
import * as fs from "fs"

import { getRepoSlug } from "./get-repo-slug"
import { InitState, InitUI } from "./interfaces"

export const createUI = (state: InitState, app: any): InitUI => {
const say = (msg: String) => console.log(msg)
const fancyLink = (name: string, href: string) => hyperLinker(name, href)
const inlineLink = (_name: string, href: string) => chalk.underline(href)
const linkToUse = state.supportsHLinks ? fancyLink : inlineLink

return {
say,
header: (msg: String) => say(chalk.bold("\n## " + msg + "\n")),
command: (command: string) => say("> " + chalk.white.bold(command) + " \n"),
link: (name: string, href: string) => linkToUse(name, href),
pause: async (secs: number) => new Promise(done => setTimeout(done, secs * 1000)),
waitForReturn: () => (app.impatient ? Promise.resolve() : readlineSync.question("\n↵ ")),
askWithAnswers: (_message: string, answers: string[]) => {
const a = readlineSync.keyInSelect(answers, "", { defaultInput: answers[0] })
return answers[a]
},
}
}

export const generateInitialState = (osProcess: NodeJS.Process): InitState => {
const isMac = osProcess.platform === "darwin"
const isWindows = osProcess.platform === "win32"
const folderName = capitalizeFirstLetter(camelCase(basename(osProcess.cwd())))
const isTypeScript = checkForTypeScript()
const isBabel = checkForBabel()
const hasTravis = fs.existsSync(".travis.yml")
const hasCircle = fs.existsSync("circle.yml")
const ciType = hasTravis ? "travis" : hasCircle ? "circle" : "unknown"

return {
isMac,
isWindows,
isTypeScript,
isBabel,
isAnOSSRepo: true,
supportsHLinks: supportsHyperlinks.stdout,
filename: isTypeScript ? "Dangerfile.ts" : "Dangerfile.js",
botName: folderName + "Bot",
hasSetUpAccount: false,
hasCreatedDangerfile: false,
hasSetUpAccountToken: false,
repoSlug: getRepoSlug(),
ciType,
}
}

const checkForTypeScript = () => fs.existsSync("node_modules/typescript/package.json")
const checkForBabel = () =>
fs.existsSync("node_modules/babel-core/package.json") || fs.existsSync("node_modules/@babel/core/package.json")

const capitalizeFirstLetter = (string: string) => string.charAt(0).toUpperCase() + string.slice(1)
const camelCase = (str: string) => str.split("-").reduce((a, b) => a + b.charAt(0).toUpperCase() + b.slice(1))

0 comments on commit e7ca5f2

Please sign in to comment.