diff --git a/.babelrc b/.babelrc
new file mode 100644
index 00000000000000..0967ef424bce67
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1 @@
+{}
diff --git a/Dangerfile.js b/Dangerfile.js
new file mode 100644
index 00000000000000..55fb0ff8f7c70e
--- /dev/null
+++ b/Dangerfile.js
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+// Removed require
+const fs = require('fs');
+const includes = require('lodash.includes');
+
+const isDocsFile = path => includes(path, 'docs/');
+const editsDocs = danger.git.modified_files.filter(isDocsFile).length > 0;
+const addsDocs = danger.git.created_files.filter(isDocsFile).length > 0;
+if (addsDocs || editsDocs) {
+ // Note, this does not yet cover edits to the autogenerated docs (e.g. comments within JS source files)
+ markdown(`:page_facing_up: Thanks for your contribution to the docs!`);
+}
+
+const isBlogFile = path => includes(path, 'blog/');
+
+// Flags new blog posts.
+const addsBlogPost = danger.git.created_files.filter(isBlogFile).length > 0;
+if (addsBlogPost) {
+ const message = ':memo: Blog post';
+ const idea = 'This PR appears to add a new blog post, and may require further review from the React Native team.';
+ warn(`${message} - ${idea}`);
+ markdown(`:memo: This PR requires attention from the @facebook/react-native team.`);
+}
+
+// Flags edits to blog posts
+const editsBlogPost = danger.git.modified_files.filter(isBlogFile).length > 0;
+if (editsBlogPost) {
+ const message = ':memo: Blog post';
+ const idea = 'This PR appears to edit an existing blog post, and may require further review from the React Native team.';
+ warn(`${message} - ${idea}`);
+ markdown(`This PR requires attention from the @facebook/react-native team.`);
+}
+
+// Fails if the description is too short.
+if (danger.github.pr.body.length < 10) {
+ fail(':grey_question: This pull request needs a description.')
+}
+
+// Warns if the PR title contains [WIP]
+const isWIP = includes(danger.github.pr.title, '[WIP]')
+if (isWIP) {
+ const message = ':construction_worker: Work In Progress';
+ const idea = 'Do not merge yet.';
+ warn(`${message} - ${idea}`);
+}
+
+// Warns if there are changes to package.json, and tags the team.
+const packageChanged = includes(danger.git.modified_files, 'package.json');
+if (packageChanged) {
+ const message = ':lock: Changes were made to package.json';
+ const idea = 'This will require a manual import. Once approved, a Facebook employee should import the PR, then run `yarn add` for any new packages.';
+ warn(`${message} - ${idea}`);
+ markdown(`This PR requires attention from the @facebook/react-native team.`);
+}
+
+// Warns if a test plan is missing.
+const gettingStartedChanged = includes(danger.git.modified_files, 'docs/GettingStarted.md');
+const includesTestPlan = danger.github.pr.body.toLowerCase().includes('test plan');
+
+// Warns if a test plan is missing, when editing the Getting Started guide. This page needs to be tested in all its permutations.
+if (!includesTestPlan && gettingStartedChanged) {
+ const message = ':clipboard: Test Plan';
+ const idea = 'This PR appears to be missing a Test Plan.';
+ warn(`${message} - ${idea}`);
+}
+// Doc edits rarely require a test plan. We'll trust the reviewer to push back if one is needed.
+if (!includesTestPlan && !editsDocs) {
+ const message = ':clipboard: Test Plan';
+ const idea = 'This PR appears to be missing a Test Plan.';
+ warn(`${message} - ${idea}`);
+}
+
+// Tags the React Native team is the PR is submitted by a core contributor
+const taskforce = fs.readFileSync('bots/IssueCommands.txt', 'utf8').split('\n')[0].split(':')[1];
+const isSubmittedByTaskforce = includes(taskforce, danger.github.pr.user.login);
+if (isSubmittedByTaskforce) {
+ markdown(`This PR has been submitted by a core contributor. Notifying @facebook/react-native.`);
+}
diff --git a/circle.yml b/circle.yml
index d5f2dead8c9ad9..5eedefe2c93373 100644
--- a/circle.yml
+++ b/circle.yml
@@ -49,6 +49,8 @@ test:
- source scripts/circle-ci-android-setup.sh && waitForAVD
override:
+ # Run Danger against PRs.
+ - if [[ $CI_PULL_REQUEST ]]; then DANGER_GITHUB_API_TOKEN="5fc403da51e5e05666e1e3ae6380190178a1cdb3" npm run danger; fi
# eslint bot. This GitHub token grants public_repo access scope.
- cat <(echo eslint; npm run lint --silent -- --format=json; echo flow; npm run flow --silent -- check --json) | GITHUB_TOKEN="af6ef0d15709bc91d""06a6217a5a826a226fb57b7" CI_USER=$CIRCLE_PROJECT_USERNAME CI_REPO=$CIRCLE_PROJECT_REPONAME PULL_REQUEST_NUMBER=$CIRCLE_PR_NUMBER node bots/code-analysis-bot.js
- npm run lint
diff --git a/package.json b/package.json
index 8637b078297611..e3581d08c33d44 100644
--- a/package.json
+++ b/package.json
@@ -126,7 +126,8 @@
"test-android-all": "npm run test-android-build && npm run test-android-run-unit && npm run test-android-run-instrumentation && npm run test-android-run-e2e",
"test-android-instrumentation": "npm run test-android-build && npm run test-android-run-instrumentation",
"test-android-unit": "npm run test-android-build && npm run test-android-run-unit",
- "test-android-e2e": "npm run test-android-build && npm run test-android-run-e2e"
+ "test-android-e2e": "npm run test-android-build && npm run test-android-run-e2e",
+ "danger": "node ./node_modules/.bin/danger"
},
"bin": {
"react-native": "local-cli/wrong-react-native.js"
@@ -220,6 +221,7 @@
},
"devDependencies": {
"babel-eslint": "^7.2.3",
+ "danger": "^0.21.2",
"eslint": "^3.19.0",
"eslint-config-fb-strict": "^20.0.3",
"eslint-config-fbjs": "^1.1.1",