diff --git a/assets/pledge-test.png b/assets/pledge-test.png
new file mode 100644
index 0000000..455b06d
Binary files /dev/null and b/assets/pledge-test.png differ
diff --git a/assets/pledge.png b/assets/pledge.png
new file mode 100644
index 0000000..bee6b07
Binary files /dev/null and b/assets/pledge.png differ
diff --git a/config-ci.json b/config-ci.json
index 133d97c..bb86ea8 100644
--- a/config-ci.json
+++ b/config-ci.json
@@ -1,9 +1,10 @@
{
"db": "db-test",
- "domain": "pledge.our.buildo.io",
+ "domain": "ci.pledge.com.fake",
"interval": 1,
"slack": {
- "incomingWebhookURL": "https://hooks.slack.com/services/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"
+ "clientId": "123456789.123456789",
+ "clientSecret": "abcdef123456789"
}
}
diff --git a/config-prod.json b/config-prod.json
index 5613bc4..7b2504d 100644
--- a/config-prod.json
+++ b/config-prod.json
@@ -1,9 +1,10 @@
{
"db": "/var/lib/pledge/db",
- "domain": "pledge.our.buildo.io",
+ "domain": "https://pledge.our.buildo.io",
"interval": 1,
"slack": {
- "incomingWebhookURL": "https://hooks.slack.com/services/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"
+ "clientId": "2194261799.137771655351",
+ "clientSecret": "37ca4f24926ff3b36f39f6f2f77faaff"
}
}
diff --git a/config.json.example b/config.json.example
index 379e9eb..d00e2ea 100644
--- a/config.json.example
+++ b/config.json.example
@@ -3,7 +3,8 @@
"domain": "my.deploy.url",
"interval": 1,
"slack": {
- "incomingWebhookURL": "https://url/of/incoming/webhook"
+ "clientId": "123456789.123456789",
+ "clientSecret": "abcdef123456789"
}
}
diff --git a/nodemon.json b/nodemon.json
new file mode 100644
index 0000000..592252b
--- /dev/null
+++ b/nodemon.json
@@ -0,0 +1,3 @@
+{
+ "ignore": ["tests/*"]
+}
diff --git a/package.json b/package.json
index 03adb36..2a0b611 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"build": "npm run clean && babel src --out-dir lib && cp config.json lib || true",
"watch": "npm run clean && cp config.json lib && babel src --out-dir lib --watch",
"start": "DEBUG=pledge,http,express:* node lib/index.js",
+ "restart": "DEBUG=pledge,http,express:* nodemon --config nodemon.json lib/index.js",
"test": "jest"
},
"repository": {
@@ -27,6 +28,7 @@
},
"homepage": "https://github.com/buildo/pledge#readme",
"dependencies": {
+ "@slack/client": "^3.8.1",
"babel-core": "^6.4.5",
"babel-polyfill": "^6.4.5",
"babel-runtime": "^6.9.2",
@@ -39,6 +41,7 @@
"mockdate": "^2.0.1",
"request": "^2.75.0",
"request-promise": "^2.0.1",
+ "simple-oauth2": "^1.0.3",
"sqlite": "^2.0.2",
"supertest": "^2.0.1"
},
@@ -54,6 +57,7 @@
"babel-preset-stage-0": "^6.5.0",
"eslint": "^2.4.0",
"eslint-config-buildo": "github:buildo/eslint-config-buildo",
- "jest": "^18.1.0"
+ "jest": "^18.1.0",
+ "nodemon": "^1.11.0"
}
}
diff --git a/src/db.js b/src/db.js
index a6d874f..10c8fa1 100644
--- a/src/db.js
+++ b/src/db.js
@@ -2,14 +2,14 @@ import db from 'sqlite';
import config from '../config.json';
import { formatDate } from './utils';
-const schemaVersion = 3;
-
const createTables = async () => {
await db.run(`
CREATE TABLE pledges (
id INTEGER PRIMARY KEY AUTOINCREMENT,
+ teamId TEXT NOT NULL,
requester TEXT NOT NULL,
performer TEXT NOT NULL,
+ performerId TEXT NOT NULL,
content TEXT NOT NULL,
deadline INTEGER NOT NULL,
completed BOOLEAN NOT NULL DEFAULT 0,
@@ -18,95 +18,88 @@ const createTables = async () => {
);
`);
await db.run(`
- CREATE TABLE schemaVersions (
- version INTEGER NOT NULL,
- migrationDate INTEGER NOT NULL
+ CREATE TABLE teams (
+ teamName TEXT NOT NULL,
+ teamId TEXT NOT NULL,
+ botUserId TEXT NOT NULL,
+ botAccessToken TEXT NOT NULL,
+ createdAt INTEGER NOT NULL
);
`);
- await db.run('INSERT INTO schemaVersions values (?, ?)', schemaVersion, Date.now());
};
-const migrateIfNeeded = async () => {
- const hasSchemaVersionsTable = !!(await db.get(`
- SELECT 1 FROM sqlite_master WHERE name ='schemaVersions' and type='table';
- `));
- const currentVersion = hasSchemaVersionsTable ?
- (await db.get('SELECT max(version) as v FROM schemaVersions')).v : 1;
- console.log(`starting pledge, current DB version is ${currentVersion}`); // eslint-disable-line no-console
- if (currentVersion < 2) {
- // version 1 did not have a schemaVersions table, let's create it
- await db.run(`
- CREATE TABLE if not exists schemaVersions (
- version INTEGER NOT NULL,
- migrationDate INTEGER NOT NULL
- );`
- );
- // add first version line with migrationDate = now
- await db.run(`
- INSERT INTO schemaVersions values (2, ?)
- `, Date.now()
- );
- // perform migration v1 => v2
- await db.run(`
- ALTER TABLE pledges
- ADD COLUMN expiredNotificationSent BOOLEAN NOT NULL DEFAULT 0`
- );
- console.log('migrated DB to version 2'); // eslint-disable-line no-console
- }
+export const getTeam = (teamId) => {
+ return db.get(`
+ SELECT teamId, teamName, botUserId, botAccessToken, createdAt
+ FROM teams
+ WHERE teamId = ?
+ `, teamId
+ );
+};
- if (currentVersion < 3) {
- // add version line with migrationDate = now
- await db.run(`
- INSERT INTO schemaVersions values (3, ?)
- `, Date.now()
- );
- // perform migration v2 => v3
- await db.run(`
- ALTER TABLE pledges
- ADD COLUMN completed BOOLEAN NOT NULL DEFAULT 0`
- );
- console.log('migrated DB to version 3'); // eslint-disable-line no-console
- }
+export const deleteTeam = (teamId) => {
+ return db.run(`
+ DELETE FROM teams
+ WHERE teamId = ?
+ `, teamId
+ );
+};
+
+export const insertTeam = (teamId, teamName, botUserId, botAccessToken) => {
+ return db.run(`
+ INSERT INTO teams (teamId, teamName, botUserId, botAccessToken, createdAt)
+ VALUES (?, ?, ?, ?, ?)
+ `, teamId, teamName, botUserId, botAccessToken, Date.now()
+ );
};
-export const getList = async requester => {
+export const getList = async (requester, teamId) => {
const requests = (await db.all(`
- SELECT id, requester, performer, content, deadline
+ SELECT id, teamId, requester, performer, content, deadline
FROM pledges
- WHERE requester = ? AND completed = 0
- `, requester)).map(x => ({ ...x, deadline: formatDate(new Date(x.deadline)) }));
+ WHERE requester = ? AND teamId = ? AND completed = 0
+ `, requester, teamId)).map(x => ({ ...x, deadline: formatDate(new Date(x.deadline)) }));
const pledges = (await db.all(`
- SELECT id, requester, performer, content, deadline
+ SELECT id, teamId, requester, performer, content, deadline
FROM pledges
- WHERE performer = ? AND completed = 0
- `, requester)).map(x => ({ ...x, deadline: formatDate(new Date(x.deadline)) }));
+ WHERE performer = ? AND teamId = ? AND completed = 0
+ `, requester, teamId)).map(x => ({ ...x, deadline: formatDate(new Date(x.deadline)) }));
return { requests, pledges };
};
export const getPledge = (pledgeId) => {
return db.get(`
- SELECT id, requester, performer, content, deadline
+ SELECT id, teamId, requester, performer, performerId, content, deadline
FROM pledges
WHERE id = ?
`, pledgeId
);
};
-export const insertPledge = ({ requester, performer, content, deadline }) => {
+export const insertPledge = ({ teamId, requester, performer, performerId, content, deadline }) => {
return db.run(`
- INSERT INTO pledges (requester, performer, content, deadline, created_at)
- VALUES (?, ?, ?, ?, ?)
- `, requester, performer, content, deadline, Date.now()
+ INSERT INTO pledges (teamId, requester, performer, performerId, content, deadline, created_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ `, teamId, requester, performer, performerId, content, deadline, Date.now()
+ );
+};
+
+export const getTeamByBotAccessToken = (botAccessToken) => {
+ return db.get(`
+ SELECT teamId, teamName, botUserId, botAccessToken, createdAt
+ FROM teams
+ WHERE botAccessToken = ?
+ `, botAccessToken
);
};
export const findAllPledgesExpiredToNotify = () => {
return db.all(`
- SELECT id, requester, performer, content, deadline
+ SELECT id, teamId, requester, performer, performerId, content, deadline
FROM pledges
- WHERE deadline < ? AND expiredNotificationSent = 0
+ WHERE deadline < ? AND expiredNotificationSent = 0 AND completed = 0
`, Date.now()
);
};
@@ -146,11 +139,8 @@ export const init = async (dbFilename = config.db) => {
const hasPledgesTable = !!(await db.get(`
SELECT 1 FROM sqlite_master WHERE name ='pledges' and type='table';
`));
- if (hasPledgesTable) {
- await migrateIfNeeded();
- } else {
- console.log('creating tables'); // eslint-disable-line no-console
- createTables();
+ if (!hasPledgesTable) {
+ await createTables();
}
};
diff --git a/src/routes.js b/src/routes.js
index f2a5631..158f39f 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -1,4 +1,5 @@
import express from 'express';
+import config from '../config.json';
import bodyParser from 'body-parser';
import _debug from 'debug';
import human2date from 'date.js';
@@ -8,9 +9,23 @@ import * as slack from './slack';
const debug = _debug('pledge');
-async function newPledge({ text, requester }) {
- const [, performer, content, humanReadableDeadline] = /(@[a-zA-Z0-9]+) (.+) by (.+)/.exec(text.trim()) || [];
+const credentials = {
+ client: {
+ id: config.slack.clientId,
+ secret: config.slack.clientSecret
+ },
+ auth: {
+ tokenHost: 'https://slack.com',
+ tokenPath: '/api/oauth.access',
+ authorizePath: '/oauth/authorize'
+ }
+};
+
+const oauth2 = require('simple-oauth2').create(credentials);
+async function newPledge({ text, requester }, botAccessToken) {
+ const [, , performerId, _performer, content, humanReadableDeadline] = /(\<@([a-zA-Z0-9]+)\|([a-zA-Z0-9]+)\>) (.+) by (.+)/.exec(text.trim()) || [];
+ const performer = `@${_performer}`;
if (!performer) {
throw new Error('"Username" is missing. (@username [what] by [when])');
} else if (!content) {
@@ -24,21 +39,23 @@ async function newPledge({ text, requester }) {
if (deadline.getTime() < Date.now()) {
throw new Error('"When" should be in the future');
}
-
- await db.insertPledge({ requester, performer, content, deadline }).then(debug);
+ const teamId = (await db.getTeamByBotAccessToken(botAccessToken)).teamId;
+ await db.insertPledge({ teamId, requester, performer, performerId, content, deadline }).then(debug);
await slack.postOnSlack({
text: `${requester} asked you to "${content}" by ${humanReadableDeadline} (${formatDate(deadline)})`,
- channel: performer
- });
+ channel: performerId
+
+ }, botAccessToken);
return `You asked ${performer} to "${content}" by ${humanReadableDeadline} (${formatDate(deadline)})`;
}
-async function getPledgesList(requester) {
- const { requests, pledges } = await db.getList(requester);
+async function getPledgesList(requester, botAccessToken) {
+ const teamId = (await db.getTeamByBotAccessToken(botAccessToken)).teamId;
+ const { requests, pledges } = await db.getList(requester, teamId);
- const baseURL = 'https://pledge.our.buildo.io';
+ const baseURL = config.domain;
const myPledges = `*My pledges:*\n${pledges.map(p => `\n • ${p.content} _for ${p.requester}_ *by ${p.deadline}* <${baseURL}/deletePledge/${p.id}|delete> <${baseURL}/completePledge/${p.id}|complete>`)}`;
const myRequests = `*My requests:*\n${requests.map(p => `\n • _${p.performer}_ pledged to ${p.content} *by ${p.deadline}* <${baseURL}/deletePledge/${p.id}|delete> <${baseURL}/completePledge/${p.id}|complete>`)}`;
@@ -49,12 +66,13 @@ async function getPledgesList(requester) {
export async function findNewNotifications() {
// notify for pledges that have expired
const expiredPledges = await db.findAllPledgesExpiredToNotify();
- expiredPledges.map(async p => {
+ await Promise.all(expiredPledges.map(async p => {
+ const botAccessToken = (await db.getTeam(p.teamId)).botAccessToken;
await slack.postOnSlackMultipleChannels({
text: `pledge ${p.content} expired just now`
- }, [p.requester, p.performer]);
+ }, [p.requester, p.performer], botAccessToken);
await db.setExpiredNotificationAsSentOnPledge(p.id);
- });
+ }));
}
setInterval(findNewNotifications, 60 * 1000);
@@ -65,24 +83,43 @@ const router = express.Router();
router.use(bodyParser.urlencoded({ extended: false }));
-router.post('/slackCommand', async ({ body: { text, user_name } }, res) => {
+router.get('/callback', async (req, res) => {
+ const code = req.query.code;
+ const options = { code };
+
+ oauth2.authorizationCode.getToken(options, async (error, result) => {
+ if (error) {
+ console.error('Access Token Error', error.message); // eslint-disable-line no-console
+ return res.json('Authentication failed');
+ }
+ await db.deleteTeam(result.team_id);
+ await db.insertTeam(result.team_id, result.team_name, result.bot.bot_user_id,
+ result.bot.bot_access_token);
+ return res
+ .status(200)
+ .send('You have successfully installed pledge ;-)');
+ });
+});
+
+router.post('/slackCommand', async ({ body: { text, user_name, team_id } }, res) => {
debug({ text, user_name });
const requester = `@${user_name}`;
if (typeof text === 'undefined' || typeof user_name === 'undefined') {
- res.status(422).send('command does not respect Slack POST format');
+ return res.status(422).send('command does not respect Slack POST format');
}
+ const team = await db.getTeam(team_id);
try {
switch (text.trim()) {
case 'list':
- return res.send(await getPledgesList(requester));
+ return res.send(await getPledgesList(requester, team.botAccessToken));
default:
- return res.send(await newPledge({ text, requester }));
+ return res.send(await newPledge({ text, requester }, team.botAccessToken));
}
} catch (err) {
debug(err);
- res.send(`Error: ${err.message}`);
+ return res.send(`Error: ${err.message}`);
}
});
@@ -92,17 +129,12 @@ router.get('/deletePledge/:pledgeId', async ({ params: { pledgeId } }, res) => {
await db.deletePledge(pledgeId);
// notify on slack
const notificationMessage = `pledge "${content}" has been deleted`;
- await slack.postOnSlack({
- text: notificationMessage,
- channel: performer
- });
- await slack.postOnSlack({
- text: notificationMessage,
- channel: requester
- });
- res.send(`Successfully deleted pledge #${pledgeId}`);
+ await slack.postOnSlackMultipleChannels({
+ text: notificationMessage
+ }, [requester, performer]);
+ return res.send(`Successfully deleted pledge #${pledgeId}`);
} catch (e) {
- res.send(`Error: ${e.message}`);
+ return res.send(`Error: ${e.message}`);
}
});
@@ -112,18 +144,31 @@ router.get('/completePledge/:pledgeId', async ({ params: { pledgeId } }, res) =>
await db.completePledge(pledgeId);
// notify on slack
const notificationMessage = `pledge "${content}" has been completed !!!`;
- await slack.postOnSlack({
- text: notificationMessage,
- channel: performer
- });
- await slack.postOnSlack({
- text: notificationMessage,
- channel: requester
- });
- res.send(`Successfully completed pledge #${pledgeId}`);
+ await slack.postOnSlackMultipleChannels({
+ text: notificationMessage
+ }, [requester, performer]);
+ return res.send(`Successfully completed pledge #${pledgeId}`);
} catch (e) {
- res.send(`Error: ${e.message}`);
+ return res.send(`Error: ${e.message}`);
}
});
+router.get('/add-to-slack', async (req, res) => {
+ res.setHeader('Content-Type', 'text/html');
+ const html = `
+
+
+
+ Install pledge in your Slack team
+
+
+
+
+
+ `;
+ return res.send(html);
+});
+
export default router;
diff --git a/src/slack.js b/src/slack.js
index 237a151..f908cdb 100644
--- a/src/slack.js
+++ b/src/slack.js
@@ -1,13 +1,21 @@
-import request from 'request-promise';
-import config from '../config.json';
+const WebClient = require('@slack/client').WebClient;
-const INCOMING_WEBHOOK_URL = config.slack.incomingWebhookURL;
-
-export const postOnSlack = json => request({
- json: { username: 'pledge', icon_emoji: ':dog:', ...json },
- url: INCOMING_WEBHOOK_URL,
- method: 'POST'
-});
+export const postOnSlack = async (json, botAccessToken) => {
+ const web = new WebClient(botAccessToken);
+ web.im.list((err, result) => {
+ if (err) {
+ return console.log(err);
+ } else {
+ const imChannel = result.ims.find((im) => im.user === json.channel).id;
+ return web.chat.postMessage(imChannel, json.text, json.opts, (err) => {
+ if (err) {
+ console.log('Error:', err);
+ }
+ });
+ }
+ });
+};
export const postOnSlackMultipleChannels =
- (json, channels) => Promise.all(channels.map(c => postOnSlack({ channel: c, ...json })));
+ (json, channels, botAccessToken) => Promise.all(channels.map(c =>
+ postOnSlack({ channel: c, ...json }, botAccessToken)));
diff --git a/tests/index.test.js b/tests/index.test.js
index de1f72a..e3b2211 100644
--- a/tests/index.test.js
+++ b/tests/index.test.js
@@ -10,12 +10,31 @@ import * as slack from '../src/slack';
slack.postOnSlack.mockImplementation(() => Promise.resolve());
slack.postOnSlackMultipleChannels.mockImplementation(() => Promise.resolve());
+const getList = () => {
+ return request(app).post('/slackCommand')
+ .send('user_name=requester')
+ .send('text=list')
+ .send('team_id=TEAM_ID')
+ .expect(200);
+};
+
+const createPledge = (by) => {
+ return request(app).post('/slackCommand')
+ .send('user_name=requester')
+ .send(`text=<@U123456789|performer> content by ${by}`)
+ .send('team_id=TEAM_ID')
+ .expect(200);
+};
+
describe('app', () => {
describe('slackCommand', () => {
beforeEach(async () => {
+ slack.postOnSlackMultipleChannels.mockClear();
+ slack.postOnSlack.mockClear();
await db.init(`db-${Math.random().toString(36).substr(2, 20)}`);
+ await db.insertTeam('TEAM_ID', 'TEAM_NAME', 'BOT_USER_ID', 'BOT_ACCESS_TOKEN');
});
afterEach(async () => {
@@ -35,65 +54,180 @@ describe('app', () => {
});
});
- it('shows an empty list of pledges with the list command', () => {
- return request(app).post('/slackCommand')
- .send('user_name=luca')
- .send('text=list')
- .expect(200)
- .then((res) => {
+ it('list: shows an empty list of pledges with the list command', () => {
+ return getList().then((res) => {
+ expect(typeof res.text).toBe('string');
+ // headers for lists are presents
+ expect(res.text).toMatch('My pledges');
+ expect(res.text).toMatch('My requests');
+ // no pledge is present (empty lists)
+ expect(res.text).not.toMatch('pledged to');
+ });
+ });
+
+ it('create: notifies both immediately, appears in list', () => {
+ MockDate.set(0);
+ return createPledge('tomorrow at 10am').then((res) => {
+ expect(typeof res.text).toBe('string');
+ // notification for requester sent as response to slack command
+ expect(res.text).toMatch('You asked @performer to \"content\" by tomorrow at 10am');
+ // notification for performer
+ expect(slack.postOnSlack.mock.calls[0][0].text)
+ .toMatch('@requester asked you to \"content\" by tomorrow at 10am');
+ expect(slack.postOnSlack.mock.calls[0][0].channel)
+ .toMatch('U123456789');
+ return getList().then((res) => {
expect(typeof res.text).toBe('string');
- // headers for lists are presents
- expect(res.text).toMatch('My pledges');
- expect(res.text).toMatch('My requests');
- // no pledge is present (empty lists)
- expect(res.text).not.toMatch('pledged to');
+ // pledge is present
+ expect(res.text).toMatch('_@performer_ pledged to content *by 2 January at 10:00*');
});
+ });
});
- it('notifies both requester and performer when a pledge is created', () => {
- return request(app).post('/slackCommand')
- .send('user_name=requester')
- .send('text=@performer pledge content by tomorrow')
- .expect(200)
- .then((res) => {
- expect(typeof res.text).toBe('string');
- // notification for requester sent as response to slack command
- expect(res.text).toMatch('You asked @performer to \"pledge content\" by tomorrow');
- // notification for performer
- expect(slack.postOnSlack.mock.calls[0][0].text)
- .toMatch('@requester asked you to \"pledge content\" by tomorrow');
- expect(slack.postOnSlack.mock.calls[0][0].channel)
- .toMatch('@performer');
+ it('expired: notifies both at the right time, stays in list', () => {
+ const timezoneOffset = new Date().getTimezoneOffset();
+ const interval = 10 * 60 * 60 * 1000; // 10 hours
+ const adjInterval = interval + timezoneOffset * 60 * 1000;
+ MockDate.set(0);
+ return createPledge('today at 10am').then(async () => {
+ // number of notifications sent to both performer and requester
+ const nExp = () =>
+ slack.postOnSlackMultipleChannels.mock.calls.filter((c) => {
+ return c[0].text.match(/expired/) && c[1].includes('@performer')
+ && c[1].includes('@requester') && c[1].length === 2;
+ }).length;
+ // just before, no notifications
+ MockDate.set(adjInterval - 1);
+ await findNewNotifications();
+ expect(nExp()).toBe(0);
+ // right after, it notifies
+ MockDate.set(adjInterval + 1);
+ await findNewNotifications();
+ expect(nExp()).toBe(1);
+ // after some time, does not notify twice
+ MockDate.set(adjInterval + 999);
+ await findNewNotifications();
+ expect(nExp()).toBe(1);
+ // check that it's still present in list
+ return getList().then((res) => {
+ // pledge is present
+ expect(res.text).toMatch('_@performer_ pledged to content *by 1 January at 10:00*');
});
+ });
});
- it('notifies both requester and performer when a pledge has expired', () => {
+ it('expired: does not notify for completed pledges', () => {
const timezoneOffset = new Date().getTimezoneOffset();
const interval = 10 * 60 * 60 * 1000; // 10 hours
const adjInterval = interval + timezoneOffset * 60 * 1000;
MockDate.set(0);
- return request(app).post('/slackCommand')
- .send('user_name=requester')
- .send('text=@performer pledge content by today at 10am')
- .expect(200)
- .then(async () => {
+ return createPledge('today at 10am').then(async () => {
+ return request(app).get('/completePledge/1').expect(200).then(async () => {
const nExp = () =>
- slack.postOnSlackMultipleChannels.mock.calls.filter((c) => c[0].text.match(/expired/)).length;
- // just before, no notifications
- MockDate.set(adjInterval - 1);
+ slack.postOnSlackMultipleChannels.mock.calls.filter((c) => {
+ return c[0].text.match(/expired/) && c[1].includes('@performer')
+ && c[1].includes('@requester') && c[1].length === 2;
+ }).length;
+ // after some time, no notification ever arrived
+ MockDate.set(adjInterval + 999);
await findNewNotifications();
expect(nExp()).toBe(0);
- // right after, it notifies
- MockDate.set(adjInterval + 1);
- await findNewNotifications();
- expect(nExp()).toBe(1);
- // after some time, does not notify twice
+ });
+ });
+ });
+
+ it('expired: does not notify for deleted pledges', () => {
+ const timezoneOffset = new Date().getTimezoneOffset();
+ const interval = 10 * 60 * 60 * 1000; // 10 hours
+ const adjInterval = interval + timezoneOffset * 60 * 1000;
+ MockDate.set(0);
+ return createPledge('today at 10am').then(async () => {
+ return request(app).get('/deletePledge/1').expect(200).then(async () => {
+ const nExp = () =>
+ slack.postOnSlackMultipleChannels.mock.calls.filter((c) => {
+ return c[0].text.match(/expired/) && c[1].includes('@performer')
+ && c[1].includes('@requester') && c[1].length === 2;
+ }).length;
+ // after some time, no notification ever arrived
MockDate.set(adjInterval + 999);
await findNewNotifications();
- expect(nExp()).toBe(1);
+ expect(nExp()).toBe(0);
});
+ });
});
- });
+ it('delete: notifies both immediately, disappears from list', () => {
+ MockDate.set(0);
+ // create a pledge
+ return createPledge('tomorrow at 10am').then(() => {
+ return getList().then((res) => {
+ // pledge is present in list
+ expect(res.text).toMatch('_@performer_ pledged to content *by 2 January at 10:00*');
+ // delete the pledge
+ return request(app).get('/deletePledge/1').expect(200).then((res) => {
+ expect(res.text).toEqual('Successfully deleted pledge #1');
+ // notifications are sent
+ expect(slack.postOnSlackMultipleChannels.mock.calls
+ .filter((x) => x[0].text.match('has been deleted'))[0]).toEqual([
+ { text: 'pledge "content" has been deleted' },
+ ['@requester', '@performer']
+ ]);
+ return getList().then((res) => {
+ // pledge is not in list any more
+ expect(res.text).not.toMatch('_@performer_ pledged to content *by 2 January at 10:00*');
+ });
+ });
+ });
+ });
+ });
+ it('complete: notifies both immediately, disappears from list', () => {
+ MockDate.set(0);
+ // create a pledge
+ return createPledge('tomorrow at 10am').then(() => {
+ return getList().then((res) => {
+ // pledge is present in list
+ expect(res.text).toMatch('_@performer_ pledged to content *by 2 January at 10:00*');
+ // delete the pledge
+ return request(app).get('/completePledge/1').expect(200).then((res) => {
+ expect(res.text).toEqual('Successfully completed pledge #1');
+ // notifications are sent
+ expect(slack.postOnSlackMultipleChannels.mock.calls
+ .filter((x) => x[0].text.match('has been completed'))[0]).toEqual([
+ { text: 'pledge "content" has been completed !!!' },
+ ['@requester', '@performer']
+ ]);
+ return getList().then((res) => {
+ // pledge is not in list any more
+ expect(res.text).not.toMatch('_@performer_ pledged to content *by 2 January at 10:00*');
+ });
+ });
+ });
+ });
+ });
+
+ it('complete: notifies both immediately, even if expired', () => {
+ MockDate.set(0);
+ const timezoneOffset = new Date().getTimezoneOffset();
+ const interval = 10 * 60 * 60 * 1000; // 10 hours
+ const adjInterval = interval + timezoneOffset * 60 * 1000;
+ // create a pledge
+ return createPledge('today at 10am').then(async () => {
+ // let it expire
+ MockDate.set(adjInterval + 999);
+ await findNewNotifications();
+ // complete the pledge
+ return request(app).get('/completePledge/1').expect(200).then((res) => {
+ expect(res.text).toEqual('Successfully completed pledge #1');
+ // notifications are sent
+ expect(slack.postOnSlackMultipleChannels.mock.calls
+ .filter((x) => x[0].text.match('has been completed'))[0]).toEqual([
+ { text: 'pledge "content" has been completed !!!' },
+ ['@requester', '@performer']
+ ]);
+ });
+ });
+ });
+
+ });
});
diff --git a/yarn.lock b/yarn.lock
index 5d905d1..1ab72fa 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,6 +2,23 @@
# yarn lockfile v1
+"@slack/client@^3.8.1":
+ version "3.8.1"
+ resolved "https://registry.yarnpkg.com/@slack/client/-/client-3.8.1.tgz#ac1744f3e87ebd52f60979ecc084c615544caeeb"
+ dependencies:
+ async "^1.5.0"
+ bluebird "^3.3.3"
+ eventemitter3 "^1.1.1"
+ https-proxy-agent "^1.0.0"
+ inherits "^2.0.1"
+ lodash "^4.13.1"
+ pkginfo "^0.4.0"
+ request "^2.64.0"
+ retry "^0.9.0"
+ url-join "0.0.1"
+ winston "^2.1.1"
+ ws "^1.0.1"
+
abab@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d"
@@ -41,6 +58,13 @@ acorn@^4.0.1:
version "4.0.4"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.4.tgz#17a8d6a7a6c4ef538b814ec9abac2779293bf30a"
+agent-base@2:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-2.0.1.tgz#bd8f9e86a8eb221fffa07bd14befd55df142815e"
+ dependencies:
+ extend "~3.0.0"
+ semver "~5.0.1"
+
ajv-keywords@^1.0.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.0.tgz#c11e6859eafff83e0dafc416929472eca946aa2c"
@@ -160,7 +184,7 @@ async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
-async@^1.4.0, async@^1.4.2, async@^1.5.2:
+async@^1.4.0, async@^1.4.2, async@^1.5.0, async@^1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
@@ -174,6 +198,10 @@ async@~0.2.6:
version "0.2.10"
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
+async@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9"
+
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -888,10 +916,14 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
-bluebird@^2.3:
+bluebird@^2.10.1, bluebird@^2.3:
version "2.11.0"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
+bluebird@^3.3.3:
+ version "3.4.7"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
+
body-parser@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.15.2.tgz#d7578cf4f1d11d5f6ea804cef35dc7a7ff6dae67"
@@ -1002,7 +1034,7 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
-chokidar@^1.0.0:
+chokidar@^1.0.0, chokidar@^1.4.3:
version "1.6.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2"
dependencies:
@@ -1072,7 +1104,7 @@ code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
-colors@1.0.3:
+colors@1.0.3, colors@1.0.x:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
@@ -1104,6 +1136,19 @@ concat-stream@^1.4.6:
readable-stream "^2.2.2"
typedarray "^0.0.6"
+configstore@^1.0.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/configstore/-/configstore-1.4.0.tgz#c35781d0501d268c25c54b8b17f6240e8a4fb021"
+ dependencies:
+ graceful-fs "^4.1.2"
+ mkdirp "^0.5.0"
+ object-assign "^4.0.1"
+ os-tmpdir "^1.0.0"
+ osenv "^0.1.0"
+ uuid "^2.0.1"
+ write-file-atomic "^1.1.2"
+ xdg-basedir "^2.0.0"
+
console-control-strings@^1.0.0, console-control-strings@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
@@ -1160,6 +1205,10 @@ cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0":
dependencies:
cssom "0.3.x"
+cycle@1.0.x:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
+
d@^0.1.1, d@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309"
@@ -1172,6 +1221,10 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
+date-fns@^1.3.0:
+ version "1.27.2"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.27.2.tgz#ce82f420bc028356cc661fc55c0494a56a990c9c"
+
date.js@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/date.js/-/date.js-0.3.1.tgz#39e7c7c77adc765d10becf496cacd391332d7cc8"
@@ -1185,7 +1238,7 @@ date.js@^0.3.1:
lodash.partition "^4.2.0"
lodash.trim "^4.2.0"
-debug@^2.1.1, debug@^2.2.0:
+debug@2, debug@^2.1.1, debug@^2.2.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b"
dependencies:
@@ -1258,6 +1311,19 @@ doctrine@^1.2.2:
esutils "^2.0.2"
isarray "^1.0.0"
+duplexer@~0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
+
+duplexify@^3.2.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.0.tgz#1aa773002e1578457e9d9d4a50b0ccaaebcbd604"
+ dependencies:
+ end-of-stream "1.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+ stream-shift "^1.0.0"
+
ecc-jsbn@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
@@ -1272,6 +1338,12 @@ encodeurl@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20"
+end-of-stream@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.0.0.tgz#d4596e702734a93e40e9af864319eabd99ff2f0e"
+ dependencies:
+ once "~1.3.0"
+
"errno@>=0.1.1 <0.2.0-0":
version "0.1.4"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d"
@@ -1310,6 +1382,10 @@ es6-map@^0.1.3:
es6-symbol "~3.1.0"
event-emitter "~0.3.4"
+es6-promise@^3.0.2:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613"
+
es6-set@~0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.4.tgz#9516b6761c2964b92ff479456233a247dc707ce8"
@@ -1474,6 +1550,22 @@ event-emitter@~0.3.4:
d "~0.1.1"
es5-ext "~0.10.7"
+event-stream@~3.3.0:
+ version "3.3.4"
+ resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571"
+ dependencies:
+ duplexer "~0.1.1"
+ from "~0"
+ map-stream "~0.1.0"
+ pause-stream "0.0.11"
+ split "0.3"
+ stream-combiner "~0.0.4"
+ through "~2.3.1"
+
+eventemitter3@^1.1.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
+
exec-sh@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.0.tgz#14f75de3f20d286ef933099b2ce50a90359cef10"
@@ -1527,7 +1619,7 @@ express@^4.13.4:
utils-merge "1.0.0"
vary "~1.1.0"
-extend@^3.0.0, extend@~3.0.0:
+extend@3, extend@^3.0.0, extend@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4"
@@ -1541,6 +1633,10 @@ extsprintf@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550"
+eyes@0.1.x:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
+
fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
@@ -1654,6 +1750,10 @@ fresh@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f"
+from@~0:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/from/-/from-0.1.3.tgz#ef63ac2062ac32acf7862e0d40b44b896f22f3bc"
+
fs-readdir-recursive@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz#8cd1745c8b4f8a29c8caec392476921ba195f560"
@@ -1769,7 +1869,22 @@ globby@^5.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
-graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6:
+got@^3.2.0:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/got/-/got-3.3.1.tgz#e5d0ed4af55fc3eef4d56007769d98192bcb2eca"
+ dependencies:
+ duplexify "^3.2.0"
+ infinity-agent "^2.0.0"
+ is-redirect "^1.0.0"
+ is-stream "^1.0.0"
+ lowercase-keys "^1.0.0"
+ nested-error-stacks "^1.0.0"
+ object-assign "^3.0.0"
+ prepend-http "^1.0.0"
+ read-all-stream "^3.0.0"
+ timed-out "^2.0.0"
+
+graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
@@ -1827,6 +1942,10 @@ hoek@2.x.x:
version "2.16.3"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
+hoek@4.x.x:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.1.0.tgz#4a4557460f69842ed463aa00628cc26d2683afa7"
+
home-or-tmp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
@@ -1860,10 +1979,22 @@ http-signature@~1.1.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
+https-proxy-agent@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz#35f7da6c48ce4ddbfa264891ac593ee5ff8671e6"
+ dependencies:
+ agent-base "2"
+ debug "2"
+ extend "3"
+
iconv-lite@0.4.13, iconv-lite@^0.4.13:
version "0.4.13"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2"
+ignore-by-default@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
+
ignore@^3.1.2:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.0.tgz#8d88f03c3002a0ac52114db25d2c673b0bf1e435"
@@ -1872,6 +2003,10 @@ imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+infinity-agent@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/infinity-agent/-/infinity-agent-2.0.3.tgz#45e0e2ff7a9eb030b27d62b74b3744b7a7ac4216"
+
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -1994,6 +2129,10 @@ is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4:
jsonpointer "^4.0.0"
xtend "^4.0.0"
+is-npm@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
+
is-number@^2.0.2, is-number@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
@@ -2028,12 +2167,20 @@ is-property@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
+is-redirect@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
+
is-resolvable@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62"
dependencies:
tryit "^1.0.1"
+is-stream@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
+
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -2046,6 +2193,10 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+isemail@2.x.x:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/isemail/-/isemail-2.2.1.tgz#0353d3d9a62951080c262c2aa0a42b8ea8e9e2a6"
+
isexe@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0"
@@ -2056,7 +2207,7 @@ isobject@^2.0.0:
dependencies:
isarray "1.0.0"
-isstream@~0.1.2:
+isstream@0.1.x, isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@@ -2124,6 +2275,10 @@ istanbul-reports@^1.0.0:
dependencies:
handlebars "^4.0.3"
+items@2.x.x:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/items/-/items-2.1.1.tgz#8bd16d9c83b19529de5aea321acaada78364a198"
+
jest-changed-files@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-17.0.2.tgz#f5657758736996f590a51b87e5c9369d904ba7b7"
@@ -2312,6 +2467,16 @@ jodid25519@^1.0.0:
dependencies:
jsbn "~0.1.0"
+joi@^9.0.4:
+ version "9.2.0"
+ resolved "https://registry.yarnpkg.com/joi/-/joi-9.2.0.tgz#3385ac790192130cbe230e802ec02c9215bbfeda"
+ dependencies:
+ hoek "4.x.x"
+ isemail "2.x.x"
+ items "2.x.x"
+ moment "2.x.x"
+ topo "2.x.x"
+
js-tokens@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-2.0.0.tgz#79903f5563ee778cc1162e6dcf1a0027c97f9cb5"
@@ -2407,6 +2572,12 @@ kind-of@^3.0.2:
dependencies:
is-buffer "^1.0.2"
+latest-version@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-1.0.1.tgz#72cfc46e3e8d1be651e1ebb54ea9f6ea96f374bb"
+ dependencies:
+ package-json "^1.0.0"
+
lazy-cache@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
@@ -2472,10 +2643,30 @@ lodash._bindcallback@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
+lodash._createassigner@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz#838a5bae2fdaca63ac22dee8e19fa4e6d6970b11"
+ dependencies:
+ lodash._bindcallback "^3.0.0"
+ lodash._isiterateecall "^3.0.0"
+ lodash.restparam "^3.0.0"
+
lodash._getnative@^3.0.0:
version "3.9.1"
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
+lodash._isiterateecall@^3.0.0:
+ version "3.0.9"
+ resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
+
+lodash.assign@^3.0.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-3.2.0.tgz#3ce9f0234b4b2223e296b8fa0ac1fee8ebca64fa"
+ dependencies:
+ lodash._baseassign "^3.0.0"
+ lodash._createassigner "^3.0.0"
+ lodash.keys "^3.0.0"
+
lodash.assign@^4.0.0, lodash.assign@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
@@ -2487,6 +2678,13 @@ lodash.clonedeep@^3.0.0:
lodash._baseclone "^3.0.0"
lodash._bindcallback "^3.0.0"
+lodash.defaults@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-3.1.2.tgz#c7308b18dbf8bc9372d701a73493c61192bd2e2c"
+ dependencies:
+ lodash.assign "^3.0.0"
+ lodash.restparam "^3.0.0"
+
lodash.filter@^4.2.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace"
@@ -2531,11 +2729,15 @@ lodash.pickby@^4.0.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff"
+lodash.restparam@^3.0.0:
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
+
lodash.trim@^4.2.0:
version "4.5.1"
resolved "https://registry.yarnpkg.com/lodash.trim/-/lodash.trim-4.5.1.tgz#36425e7ee90be4aa5e27bcebb85b7d11ea47aa57"
-lodash@^4.0.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0:
+lodash@^4.0.0, lodash@^4.11.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@@ -2549,12 +2751,20 @@ loose-envify@^1.0.0:
dependencies:
js-tokens "^2.0.0"
+lowercase-keys@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
+
makeerror@1.0.x:
version "1.0.11"
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
dependencies:
tmpl "1.0.x"
+map-stream@~0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194"
+
marked-terminal@^1.6.2:
version "1.7.0"
resolved "https://registry.yarnpkg.com/marked-terminal/-/marked-terminal-1.7.0.tgz#c8c460881c772c7604b64367007ee5f77f125904"
@@ -2641,6 +2851,10 @@ mockdate@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-2.0.1.tgz#51bc309e2c4396600d56b6c23a6a0f4182943a36"
+moment@2.x.x:
+ version "2.17.1"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82"
+
ms@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
@@ -2665,6 +2879,12 @@ negotiator@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
+nested-error-stacks@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz#19f619591519f096769a5ba9a86e6eeec823c3cf"
+ dependencies:
+ inherits "~2.0.1"
+
node-emoji@^1.4.1:
version "1.5.0"
resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.5.0.tgz#9a0d9fe03fd43afa357d6d8e439aa31e599959b7"
@@ -2701,6 +2921,27 @@ node-pre-gyp@^0.6.29, node-pre-gyp@~0.6.31:
tar "~2.2.1"
tar-pack "~3.3.0"
+nodemon@^1.11.0:
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.11.0.tgz#226c562bd2a7b13d3d7518b49ad4828a3623d06c"
+ dependencies:
+ chokidar "^1.4.3"
+ debug "^2.2.0"
+ es6-promise "^3.0.2"
+ ignore-by-default "^1.0.0"
+ lodash.defaults "^3.1.2"
+ minimatch "^3.0.0"
+ ps-tree "^1.0.1"
+ touch "1.0.0"
+ undefsafe "0.0.3"
+ update-notifier "0.5.0"
+
+nopt@~1.0.10:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
+ dependencies:
+ abbrev "1"
+
nopt@~3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
@@ -2741,6 +2982,10 @@ oauth-sign@~0.8.1:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
+object-assign@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
+
object-assign@^4.0.1, object-assign@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0"
@@ -2764,7 +3009,7 @@ once@^1.3.0, once@^1.4.0:
dependencies:
wrappy "1"
-once@~1.3.3:
+once@~1.3.0, once@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20"
dependencies:
@@ -2792,6 +3037,10 @@ optionator@^0.8.1:
type-check "~0.3.2"
wordwrap "~1.0.0"
+options@>=0.0.5:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f"
+
os-homedir@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
@@ -2802,10 +3051,17 @@ os-locale@^1.4.0:
dependencies:
lcid "^1.0.0"
-os-tmpdir@^1.0.1:
+os-tmpdir@^1.0.0, os-tmpdir@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+osenv@^0.1.0:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644"
+ dependencies:
+ os-homedir "^1.0.0"
+ os-tmpdir "^1.0.0"
+
output-file-sync@^1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/output-file-sync/-/output-file-sync-1.1.2.tgz#d0a33eefe61a205facb90092e826598d5245ce76"
@@ -2814,6 +3070,13 @@ output-file-sync@^1.1.0:
mkdirp "^0.5.1"
object-assign "^4.1.0"
+package-json@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/package-json/-/package-json-1.2.0.tgz#c8ecac094227cdf76a316874ed05e27cc939a0e0"
+ dependencies:
+ got "^3.2.0"
+ registry-url "^3.0.0"
+
parse-glob@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
@@ -2867,6 +3130,12 @@ path-type@^1.0.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
+pause-stream@0.0.11:
+ version "0.0.11"
+ resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
+ dependencies:
+ through "~2.3"
+
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@@ -2881,6 +3150,10 @@ pinkie@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
+pkginfo@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.0.tgz#349dbb7ffd38081fcadc0853df687f0c7744cd65"
+
pluralize@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
@@ -2889,6 +3162,10 @@ prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
+prepend-http@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
+
preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
@@ -2922,6 +3199,12 @@ prr@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
+ps-tree@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014"
+ dependencies:
+ event-stream "~3.3.0"
+
punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
@@ -2953,7 +3236,7 @@ raw-body@~2.1.7:
iconv-lite "0.4.13"
unpipe "1.0.0"
-rc@~1.1.6:
+rc@^1.0.1, rc@~1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.6.tgz#43651b76b6ae53b5c802f1151fa3fc3b059969c9"
dependencies:
@@ -2962,6 +3245,13 @@ rc@~1.1.6:
minimist "^1.2.0"
strip-json-comments "~1.0.4"
+read-all-stream@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa"
+ dependencies:
+ pinkie-promise "^2.0.0"
+ readable-stream "^2.0.0"
+
read-pkg-up@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
@@ -2977,7 +3267,7 @@ read-pkg@^1.0.0:
normalize-package-data "^2.3.2"
path-type "^1.0.0"
-"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.2.2:
+readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e"
dependencies:
@@ -3055,6 +3345,12 @@ regexpu-core@^2.0.0:
regjsgen "^0.2.0"
regjsparser "^0.1.4"
+registry-url@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942"
+ dependencies:
+ rc "^1.0.1"
+
regjsgen@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7"
@@ -3073,6 +3369,12 @@ repeat-string@^1.5.2:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
+repeating@^1.1.2:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/repeating/-/repeating-1.1.3.tgz#3d4114218877537494f97f77f9785fab810fa4ac"
+ dependencies:
+ is-finite "^1.0.0"
+
repeating@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
@@ -3087,7 +3389,7 @@ request-promise@^2.0.1:
lodash "^4.5.0"
request "^2.34"
-request@^2.34, request@^2.55.0, request@^2.75.0, request@^2.79.0:
+request@^2.34, request@^2.55.0, request@^2.64.0, request@^2.67.0, request@^2.75.0, request@^2.79.0:
version "2.79.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de"
dependencies:
@@ -3146,6 +3448,10 @@ restore-cursor@^1.0.1:
exit-hook "^1.0.0"
onetime "^1.0.0"
+retry@^0.9.0:
+ version "0.9.0"
+ resolved "https://registry.yarnpkg.com/retry/-/retry-0.9.0.tgz#6f697e50a0e4ddc8c8f7fb547a9b60dead43678d"
+
right-align@^0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
@@ -3183,10 +3489,20 @@ sax@^1.1.4:
version "1.2.1"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
-"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@~5.3.0:
+semver-diff@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
+ dependencies:
+ semver "^5.0.3"
+
+"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@~5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
+semver@~5.0.1:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a"
+
send@0.14.1:
version "0.14.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.14.1.tgz#a954984325392f51532a7760760e459598c89f7a"
@@ -3238,6 +3554,16 @@ signal-exit@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
+simple-oauth2@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/simple-oauth2/-/simple-oauth2-1.0.3.tgz#949e5bec0674904fde3eda5e7a87b1677dbe1ef8"
+ dependencies:
+ bluebird "^2.10.1"
+ date-fns "^1.3.0"
+ debug "^2.2.0"
+ joi "^9.0.4"
+ request "^2.67.0"
+
slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@@ -3246,6 +3572,10 @@ slice-ansi@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
+slide@^1.1.5:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
+
sntp@1.x.x:
version "1.0.9"
resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
@@ -3288,6 +3618,12 @@ spdx-license-ids@^1.0.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57"
+split@0.3:
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f"
+ dependencies:
+ through "2"
+
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@@ -3320,10 +3656,30 @@ sshpk@^1.7.0:
jsbn "~0.1.0"
tweetnacl "~0.14.0"
+stack-trace@0.0.x:
+ version "0.0.9"
+ resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.9.tgz#a8f6eaeca90674c333e7c43953f275b451510695"
+
"statuses@>= 1.3.1 < 2", statuses@~1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
+stream-combiner@~0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14"
+ dependencies:
+ duplexer "~0.1.1"
+
+stream-shift@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
+
+string-length@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/string-length/-/string-length-1.0.1.tgz#56970fb1c38558e9e70b728bf3de269ac45adfac"
+ dependencies:
+ strip-ansi "^3.0.0"
+
string-width@^1.0.1, string-width@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
@@ -3457,10 +3813,14 @@ throat@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/throat/-/throat-3.0.0.tgz#e7c64c867cbb3845f10877642f7b60055b8ec0d6"
-through@^2.3.6:
+through@2, through@^2.3.6, through@~2.3, through@~2.3.1:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
+timed-out@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a"
+
tmpl@1.0.x:
version "1.0.4"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
@@ -3469,6 +3829,18 @@ to-fast-properties@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.2.tgz#f3f5c0c3ba7299a7ef99427e44633257ade43320"
+topo@2.x.x:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182"
+ dependencies:
+ hoek "4.x.x"
+
+touch@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/touch/-/touch-1.0.0.tgz#449cbe2dbae5a8c8038e30d71fa0ff464947c4de"
+ dependencies:
+ nopt "~1.0.10"
+
tough-cookie@^2.3.1, tough-cookie@~2.3.0:
version "2.3.2"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a"
@@ -3525,10 +3897,34 @@ uid-number@~0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
+ultron@1.0.x:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
+
+undefsafe@0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-0.0.3.tgz#ecca3a03e56b9af17385baac812ac83b994a962f"
+
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+update-notifier@0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-0.5.0.tgz#07b5dc2066b3627ab3b4f530130f7eddda07a4cc"
+ dependencies:
+ chalk "^1.0.0"
+ configstore "^1.0.0"
+ is-npm "^1.0.0"
+ latest-version "^1.0.0"
+ repeating "^1.1.2"
+ semver-diff "^2.0.0"
+ string-length "^1.0.0"
+
+url-join@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/url-join/-/url-join-0.0.1.tgz#1db48ad422d3402469a87f7d97bdebfe4fb1e3c8"
+
user-home@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190"
@@ -3547,6 +3943,10 @@ utils-merge@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8"
+uuid@^2.0.1:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
+
uuid@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1"
@@ -3621,6 +4021,17 @@ window-size@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
+winston@^2.1.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/winston/-/winston-2.3.1.tgz#0b48420d978c01804cf0230b648861598225a119"
+ dependencies:
+ async "~1.0.0"
+ colors "1.0.x"
+ cycle "1.0.x"
+ eyes "0.1.x"
+ isstream "0.1.x"
+ stack-trace "0.0.x"
+
wordwrap@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
@@ -3651,12 +4062,33 @@ wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+write-file-atomic@^1.1.2:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.1.tgz#7d45ba32316328dd1ec7d90f60ebc0d845bb759a"
+ dependencies:
+ graceful-fs "^4.1.11"
+ imurmurhash "^0.1.4"
+ slide "^1.1.5"
+
write@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"
dependencies:
mkdirp "^0.5.1"
+ws@^1.0.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.1.tgz#082ddb6c641e85d4bb451f03d52f06eabdb1f018"
+ dependencies:
+ options ">=0.0.5"
+ ultron "1.0.x"
+
+xdg-basedir@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2"
+ dependencies:
+ os-homedir "^1.0.0"
+
"xml-name-validator@>= 2.0.1 < 3.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"