forked from mirrors/probot
Merge remote-tracking branch 'origin/master' into doc-deploy
* origin/master: (27 commits) Release v0.4.1. Disable raven console alerts Relax node version requirements Release v0.4.0. Update log for local server Remove tunnel script Move subdomain config Update docs to use localtunnel Add subdomain option Setup tunnel using localtunnel Ensure node version is satisfied autoBreadcrumbs Use local logger variable Connect sentry to logger Move logger setup to index Fix package.json for multiple plugins Specific filename not needed Remove log Ignore all private keys Look for any pem in the cwd ...
This commit is contained in:
commit
d46c3a25fb
|
@ -4,3 +4,6 @@ WEBHOOK_SECRET=development
|
|||
|
||||
# Uncomment this to get verbose logging
|
||||
# LOG_LEVEL=trace # or `info` to show less
|
||||
|
||||
# Subdomain to use for localtunnel server. Defaults to your local username.
|
||||
# SUBDOMAIN=
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
npm-debug.log
|
||||
.DS_Store
|
||||
node_modules/
|
||||
private-key.pem
|
||||
*.pem
|
||||
.env
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at opensource+probot@github.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
|
@ -1,5 +1,13 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const semver = require('semver');
|
||||
const version = require('../package').engines.node;
|
||||
|
||||
if (!semver.satisfies(process.version, version)) {
|
||||
console.log(`Node.js version ${version} is required. You have ${process.version}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
require('commander')
|
||||
.version(require('../package').version)
|
||||
.usage('<command> [options]')
|
||||
|
|
|
@ -17,6 +17,7 @@ program
|
|||
process.exit(1);
|
||||
}
|
||||
}, process.env.PRIVATE_KEY)
|
||||
.option('-t, --tunnel <subdomain>', 'Expose your local bot to the internet', process.env.SUBDOMAIN || process.env.NODE_ENV != 'production')
|
||||
.parse(process.argv);
|
||||
|
||||
if(!program.integration) {
|
||||
|
@ -25,14 +26,45 @@ if(!program.integration) {
|
|||
}
|
||||
|
||||
if(!program.privateKey) {
|
||||
try {
|
||||
program.privateKey = require('fs').readFileSync('private-key.pem');
|
||||
} catch (err) {
|
||||
const fs = require('fs');
|
||||
const key = fs.readdirSync(process.cwd()).find(path => path.endsWith('.pem'));
|
||||
|
||||
if (key) {
|
||||
program.privateKey = fs.readFileSync(key);
|
||||
} else {
|
||||
console.warn("Missing GitHub Integration private key.\nUse --private-key flag or set PRIVATE_KEY environment variable.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if(program.tunnel) {
|
||||
try {
|
||||
setupTunnel();
|
||||
} catch(err) {
|
||||
console.warn('Run `npm install --save-dev localtunnel` to enable localtunnel.');
|
||||
}
|
||||
}
|
||||
|
||||
function setupTunnel() {
|
||||
const localtunnel = require('localtunnel');
|
||||
const subdomain = typeof program.tunnel == 'string' ?
|
||||
program.tunnel :
|
||||
require('os').userInfo().username;
|
||||
|
||||
const tunnel = localtunnel(program.port, {subdomain}, function (err, tunnel) {
|
||||
if (err) {
|
||||
console.warn('Could not open tunnel: ', err.message);
|
||||
} else {
|
||||
console.log('Listening on ' + tunnel.url);
|
||||
tunnel.url;
|
||||
}
|
||||
});
|
||||
|
||||
tunnel.on('close', function() {
|
||||
console.warn('Local tunnel closed');
|
||||
});
|
||||
}
|
||||
|
||||
const pkgConf = require('pkg-conf');
|
||||
const resolve = require('resolve').sync;
|
||||
const createProbot = require('../');
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
# Best practices for plugins
|
||||
|
||||
## Autonomy
|
||||
|
||||
### Never take bulk actions without explicit permission
|
||||
|
||||
Being installed on an account is sufficient permission for actions in response to a user action, like replying on a single issue. But a plugin _must_ have explicit permission before performing bulk actions, like labeling all open issues.
|
||||
|
||||
For example, the [stale](https://github.com/probot/stale) plugin will only scan a repository for stale issues and pull requests if `.github/stale.yml` exists in the repository.
|
||||
|
||||
### Include "dry run" functionality
|
||||
|
||||
A dry run is when a plugin, instead of actually taking an action, only logs what actions it would have taken if it wasn't a dry run. A plugin _must_ offer a dry run feature if it does anything destructive and _should_ offer a dry run feature in all cases.
|
||||
|
||||
For example, the [stale](https://github.com/probot/stale) plugin will perform a dry run if there is no `.github/stale.yml` file in the repository.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Require minimal configuration
|
||||
|
||||
Plugins _should_ provide sensible defaults for all settings.
|
||||
|
||||
### Provide full configuration
|
||||
|
||||
Plugins _should_ allow all settings to customized for each installation.
|
||||
|
||||
### Store configuration in the repository
|
||||
|
||||
Any configuration _should_ be stored in the repository. Unless the plugin is using files from an established convention, the configuration _should_ be stored in the `.github` directory.
|
||||
|
||||
For example, the [owners](https://github.com/probot/owners) plugin reads from the `OWNERS` file, which is a convention that existed before the plugin was created, while the [configurer](https://github.com/probot/configurer) plugin reads from `.github/config.yml`.
|
|
@ -53,13 +53,13 @@ To deploy a bot that includes multiple plugins, create a new app that has the pl
|
|||
```json
|
||||
{
|
||||
"name": "my-probot",
|
||||
"priate": true,
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"probot-autoresponder": "~1.0",
|
||||
"probot-configurer": "~1.0",
|
||||
"probot-autoresponder": "probot/autoresponder",
|
||||
"probot-configurer": "probot/configurer"
|
||||
},
|
||||
"scripts": {
|
||||
"run": "probot run"
|
||||
"start": "probot run"
|
||||
},
|
||||
"probot": {
|
||||
"plugins": [
|
||||
|
|
|
@ -3,23 +3,18 @@
|
|||
To run a plugin locally, you'll need to create a GitHub Integration and configure it to deliver webhooks to your local machine.
|
||||
|
||||
1. Make sure you have a recent version of [Node.js](https://nodejs.org/) installed
|
||||
1. Install [ngrok](https://ngrok.com/download) (`$ brew cask install ngrok` on a mac), which will expose the local server to the internet so GitHub can send webhooks
|
||||
1. Run `$ ngrok http 3000` to start ngrok, which should output something like `Forwarding https://4397efc6.ngrok.io -> localhost:3000`
|
||||
1. [Create a new GitHub Integration](https://github.com/settings/integrations/new) with:
|
||||
- **Callback URL** and **Webhook URL**: The full ngrok url above. For example: `https://4397efc6.ngrok.io/`
|
||||
- **Webhook URL**: Set to `https://example.com/` and we'll update it in a minute.
|
||||
- **Webhook Secret:** `development`
|
||||
- **Permissions & events** needed will depend on how you use the bot, but for development it may be easiest to enable everything.
|
||||
1. Download the private key and move it to `private-key.pem` in the project directory
|
||||
1. Download the private key and move it to the project directory
|
||||
1. Edit `.env` and set `INTEGRATION_ID` to the ID of the integration you just created.
|
||||
1. With `ngrok` still running, open another terminal and run `$ npm start` to start the server on http://localhost:3000
|
||||
1. Run `$ npm start` to start the server, which will output `Listening on https://yourname.localtunnel.me`;
|
||||
1. Update the **Webhook URL** in the [integration settings](https://github.com/settings/integrations) to use the `localtunnel.me` URL.
|
||||
|
||||
You'll need to create a test repository and install your Integration by clicking the "Install" button on the settings page.
|
||||
|
||||
Whenever you com back to work on the app after you've already had it running once, then you need to:
|
||||
|
||||
1. Run `$ npm start`
|
||||
1. Run `$ ngrok http 3000` in another terminal window
|
||||
1. `ngrok` will use a different URL every time it is restarted, so you will have to go into the [settings for your Integration](https://github.com/settings/integrations) and update all the URLs.
|
||||
Whenever you com back to work on the app after you've already had it running once, you should only need to run `$ npm start`.
|
||||
|
||||
## Debugging
|
||||
|
||||
|
|
|
@ -107,8 +107,6 @@ $ probot run -i 9999 -P private-key.pem ./autoresponder.js
|
|||
Listening on http://localhost:3000
|
||||
```
|
||||
|
||||
Once your bot is running, you'll need to use `ngrok` to receive GitHub webhooks as described in the [development](development.md) documentation.
|
||||
|
||||
## Publishing your bot
|
||||
|
||||
Plugins can be published in NPM modules, which can either be deployed as stand-alone bots, or combined with other plugins.
|
||||
|
|
29
index.js
29
index.js
|
@ -1,7 +1,11 @@
|
|||
const bunyan = require('bunyan');
|
||||
const bunyanFormat = require('bunyan-format');
|
||||
const sentryStream = require('bunyan-sentry-stream');
|
||||
const cacheManager = require('cache-manager');
|
||||
const createWebhook = require('github-webhook-handler');
|
||||
const createIntegration = require('github-integration');
|
||||
const createWebhook = require('github-webhook-handler');
|
||||
const Raven = require('raven');
|
||||
|
||||
const createRobot = require('./lib/robot');
|
||||
const createServer = require('./lib/server');
|
||||
|
||||
|
@ -11,6 +15,12 @@ module.exports = options => {
|
|||
ttl: 60 * 60 // 1 hour
|
||||
});
|
||||
|
||||
const logger = bunyan.createLogger({
|
||||
name: 'PRobot',
|
||||
level: process.env.LOG_LEVEL || 'debug',
|
||||
stream: bunyanFormat({outputMode: process.env.LOG_FORMAT || 'short'})
|
||||
});
|
||||
|
||||
const webhook = createWebhook({path: '/', secret: options.secret});
|
||||
const integration = createIntegration({
|
||||
id: options.id,
|
||||
|
@ -18,22 +28,21 @@ module.exports = options => {
|
|||
debug: process.env.LOG_LEVEL === 'trace'
|
||||
});
|
||||
const server = createServer(webhook);
|
||||
const robot = createRobot(integration, webhook, cache);
|
||||
const robot = createRobot({integration, webhook, cache, logger});
|
||||
|
||||
if (process.env.SENTRY_URL) {
|
||||
Raven.disableConsoleAlerts();
|
||||
Raven.config(process.env.SENTRY_URL, {
|
||||
captureUnhandledRejections: true
|
||||
captureUnhandledRejections: true,
|
||||
autoBreadcrumbs: true
|
||||
}).install({});
|
||||
}
|
||||
|
||||
// Show trace for any unhandled rejections
|
||||
process.on('unhandledRejection', reason => {
|
||||
robot.log.error(reason);
|
||||
});
|
||||
logger.addStream(sentryStream(Raven));
|
||||
}
|
||||
|
||||
// Handle case when webhook creation fails
|
||||
webhook.on('error', err => {
|
||||
robot.log.error(err);
|
||||
logger.error(err);
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -42,7 +51,7 @@ module.exports = options => {
|
|||
|
||||
start() {
|
||||
server.listen(options.port);
|
||||
console.log('Listening on http://localhost:' + options.port);
|
||||
logger.trace('Listening on http://localhost:' + options.port);
|
||||
},
|
||||
|
||||
load(plugin) {
|
||||
|
|
37
lib/robot.js
37
lib/robot.js
|
@ -1,20 +1,15 @@
|
|||
const bunyan = require('bunyan');
|
||||
const bunyanFormat = require('bunyan-format');
|
||||
const GitHubApi = require('github');
|
||||
const Bottleneck = require('bottleneck');
|
||||
const Context = require('./context');
|
||||
|
||||
const logger = bunyan.createLogger({
|
||||
name: 'PRobot',
|
||||
level: process.env.LOG_LEVEL || 'debug',
|
||||
stream: bunyanFormat({outputMode: process.env.LOG_FORMAT || 'short'})
|
||||
});
|
||||
|
||||
class Robot {
|
||||
constructor(integration, webhook, cache) {
|
||||
constructor({integration, webhook, cache, logger}) {
|
||||
this.integration = integration;
|
||||
this.webhook = webhook;
|
||||
this.cache = cache;
|
||||
this.log = wrapLogger(logger);
|
||||
|
||||
this.webhook.on('*', event => this.log.trace(event, 'webhook received'));
|
||||
}
|
||||
|
||||
on(event, callback) {
|
||||
|
@ -44,10 +39,6 @@ class Robot {
|
|||
|
||||
return probotEnhancedClient(github);
|
||||
}
|
||||
|
||||
log(...args) {
|
||||
return logger.debug(...args);
|
||||
}
|
||||
}
|
||||
|
||||
function probotEnhancedClient(github) {
|
||||
|
@ -69,9 +60,21 @@ function rateLimitedClient(github) {
|
|||
return github;
|
||||
}
|
||||
|
||||
// Add level methods on the logger
|
||||
['trace', 'debug', 'info', 'warn', 'error', 'fatal'].forEach(level => {
|
||||
Robot.prototype.log[level] = logger[level].bind(logger);
|
||||
});
|
||||
// Return a function that defaults to "debug" level, and has properties for
|
||||
// other levels:
|
||||
//
|
||||
// robot.log("debug")
|
||||
// robot.log.trace("verbose details");
|
||||
//
|
||||
function wrapLogger(logger) {
|
||||
const fn = logger.debug.bind(logger);
|
||||
|
||||
// Add level methods on the logger
|
||||
['trace', 'debug', 'info', 'warn', 'error', 'fatal'].forEach(level => {
|
||||
fn[level] = logger[level].bind(logger);
|
||||
});
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
module.exports = (...args) => new Robot(...args);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "probot",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.1",
|
||||
"description": "a trainable robot that responds to activity on GitHub",
|
||||
"repository": "https://github.com/probot/probot",
|
||||
"main": "index.js",
|
||||
|
@ -17,6 +17,7 @@
|
|||
"bottleneck": "^1.15.1",
|
||||
"bunyan": "^1.8.5",
|
||||
"bunyan-format": "^0.2.1",
|
||||
"bunyan-sentry-stream": "^1.1.0",
|
||||
"cache-manager": "^2.4.0",
|
||||
"commander": "^2.9.0",
|
||||
"dotenv": "~4.0.0",
|
||||
|
@ -60,6 +61,6 @@
|
|||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": "~7.7.0"
|
||||
"node": "^7.7"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
ngrok http -bind-tls=true 3000
|
|
@ -3,13 +3,18 @@ const expect = require('expect');
|
|||
const Context = require('../lib/context');
|
||||
const createRobot = require('../lib/robot');
|
||||
|
||||
const nullLogger = {};
|
||||
['trace', 'debug', 'info', 'warn', 'error', 'fatal'].forEach(level => {
|
||||
nullLogger[level] = function () { };
|
||||
});
|
||||
|
||||
describe('Robot', () => {
|
||||
let webhook;
|
||||
let robot;
|
||||
|
||||
beforeEach(() => {
|
||||
webhook = new EventEmitter();
|
||||
robot = createRobot({}, webhook);
|
||||
robot = createRobot({webhook, logger: nullLogger});
|
||||
});
|
||||
|
||||
describe('on', () => {
|
||||
|
|
Loading…
Reference in New Issue