diff --git a/.gitignore b/.gitignore index b99ac66..3f745cc 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,5 @@ node_modules/ # dotenv environment variables file .env + +cli/dist diff --git a/cli/dist/.gitkeep b/cli/dist/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/cli/emailPreview.js b/cli/emailPreview.js new file mode 100644 index 0000000..7bf8ac4 --- /dev/null +++ b/cli/emailPreview.js @@ -0,0 +1,20 @@ +const Handlebars = require('handlebars') +const { readFileSync, writeFile } = require('fs') +const open = require('open') +const { safeLoad } = require('js-yaml') + +const templateName = process.argv[2] +const template = readFileSync(`../functions/templates/${templateName}.hbs`).toString() +const fixture = { + ...safeLoad(readFileSync(`./fixtures/${templateName}.yml`)), + translations: safeLoad(readFileSync(`../functions/locales/server/en.yml`)).templates[templateName] +} + +Handlebars.registerPartial('cadence', readFileSync('../functions/templates/cadence.hbs').toString()) +Handlebars.registerPartial('shareStyles', readFileSync('../functions/templates/shareStyles.hbs').toString()) + +writeFile( + `./dist/${templateName}.html`, + Handlebars.compile(template)(fixture), + () => open(`dist/${templateName}.html`) +) diff --git a/fixtures/original.ical b/cli/fixtures/original.ical similarity index 100% rename from fixtures/original.ical rename to cli/fixtures/original.ical diff --git a/cli/fixtures/share.yml b/cli/fixtures/share.yml new file mode 100644 index 0000000..d382432 --- /dev/null +++ b/cli/fixtures/share.yml @@ -0,0 +1,47 @@ +stats: + - figure: 8 + description: Total Scheduled Ceremonies + - figure: 2 + description: Average People Per Meeting + - figure: 5 + description: Average Ceremonies Per Week +cadences: + sprint: + - id: wednesday-1 + name: First Wednesday + ceremonies: + - id: planning + title: Sprint Planning + icon: '👓' + startTime: 10:00am + notes: Rotating team member to bring donuts + general: + - id: daily + name: Daily + ceremonies: + - id: checkin + title: Check in + icon: '🔗' + async: true + notes: We'll do a synchronous stand-up in Slack, with one person bringing a 'Question of the Day' + - id: quarterly + name: Quarterly + ceremonies: + - id: documentation + title: Documentation + icon: '📒' + async: true + people: + - initial: J + name: James + - initial: A + name: Ashlyn + - id: hackathon + title: Hackathon + icon: '🍕' + notes: To be led by the Team leads to gather ideas, we'll take 2 days to spike out some features + async: false + startTime: 9:00am + people: + - initial: J + name: James diff --git a/cli/package.json b/cli/package.json new file mode 100644 index 0000000..dc07034 --- /dev/null +++ b/cli/package.json @@ -0,0 +1,16 @@ +{ + "name": "cli", + "description": "Testing CLI for Minimum Viable Ceremonies", + "engines": { + "node": "10" + }, + "scripts": { + "preview": "node ./emailPreview.js" + }, + "private": true, + "dependencies": { + "handlebars": "^4.7.6", + "js-yaml": "^3.14.0", + "open": "^7.3.0" + } +} diff --git a/cli/yarn.lock b/cli/yarn.lock new file mode 100644 index 0000000..7efe509 --- /dev/null +++ b/cli/yarn.lock @@ -0,0 +1,85 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +handlebars@^4.7.6: + version "4.7.6" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e" + integrity sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +is-docker@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" + integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== + +is-wsl@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +js-yaml@^3.14.0: + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +neo-async@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +open@^7.3.0: + version "7.3.0" + resolved "https://registry.yarnpkg.com/open/-/open-7.3.0.tgz#45461fdee46444f3645b6e14eb3ca94b82e1be69" + integrity sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +uglify-js@^3.1.4: + version "3.11.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.11.4.tgz#b47b7ae99d4bd1dca65b53aaa69caa0909e6fadf" + integrity sha512-FyYnoxVL1D6+jDGQpbK5jW6y/2JlVfRfEeQ67BPCUg5wfCjaKOpr2XeceE4QL+MkhxliLtf5EbrMDZgzpt2CNw== + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= diff --git a/functions/common.js b/functions/common.js index c23bdc8..9be5c3c 100644 --- a/functions/common.js +++ b/functions/common.js @@ -1,10 +1,15 @@ const { database } = require('firebase-admin') const { https } = require('firebase-functions') +const Handlebars = require('handlebars') +const { readFileSync } = require('fs') const phrase = require('random-words') const setLanguage = require('./locales/node') const cors = require('cors') const fs = require('fs') +Handlebars.registerPartial('cadence', fs.readFileSync('./templates/cadence.hbs').toString()) +Handlebars.registerPartial('shareStyles', fs.readFileSync('./templates/shareStyles.hbs').toString()) + exports.endpoint = (origin, fn) => https.onRequest((req, res) => setLanguage(req).then(t => @@ -38,6 +43,43 @@ exports.createRoom = ({ return { uuid, name, features, weekCount, ceremonies } } +exports.snapshotToShare = (snapshot, t) => { + const { ceremonies = [] } = snapshot.toJSON() || {} + const cadences = Object.values(ceremonies).reduce((result, ceremony) => { + if (['void', 'undecided'].includes(ceremony.placement)) { return result } + result[ceremony.placement] = result[ceremony.placement] || { + id: ceremony.placement, + name: t(`app:cadences.${ceremony.placement}.name`), + ceremonies: [] + } + result[ceremony.placement].ceremonies.unshift({ + ...ceremony, + title: ceremony.title || t(`app:ceremonies.${ceremony.id}.name`), + pill: ( + (ceremony.async && t('templates.share.async')) || + (ceremony.startTime && dayjs().set('hour', 0).set('minute', ceremony.startTime).format('H:mm a')) + ), + people: Object.values(ceremony.people || []) + .filter(person => person.label) + .map(({ label }) => ({ initial: label[0], name: label })) + }) + return result + }, []) + + return { + cadences, + stats: { + // TODO: calculate stats + ceremonies: 6, + people: 2, + weekly: 4.3 + } + } +} + +exports.compileTemplate = (template, data) => + Handlebars.compile(readFileSync(`./templates/${template}.hbs`).toString())(data) + const setResponse = (req, res, fn, t) => Promise.resolve(fn(req, t)) .then(([status, body]) => status === 302 diff --git a/functions/locales b/functions/locales index 6aa3bbe..fe961ec 160000 --- a/functions/locales +++ b/functions/locales @@ -1 +1 @@ -Subproject commit 6aa3bbe9a0a1e6adbe0e4366a71b397f78158dc1 +Subproject commit fe961ec2d8660ae8716e1edf6167d7951819714a diff --git a/functions/package.json b/functions/package.json index db0808b..31a78f5 100644 --- a/functions/package.json +++ b/functions/package.json @@ -15,12 +15,13 @@ "accept-language-parser": "^1.5.0", "axios": "^0.20.0", "crypto": "^1.0.1", + "dayjs": "^1.9.5", "firebase-admin": "^8.10.0", "firebase-functions": "^3.11.0", + "handlebars": "^4.7.6", "i18next": "^19.8.2", "i18next-fs-backend": "^1.0.7", "ical-generator": "^1.12.1", - "moment": "^2.27.0", "querystring": "^0.2.0", "random-words": "^1.1.1", "tsscmp": "^1.0.6" diff --git a/functions/sendgrid/index.js b/functions/sendgrid/index.js index 13829d5..d272365 100644 --- a/functions/sendgrid/index.js +++ b/functions/sendgrid/index.js @@ -2,5 +2,6 @@ const { config } = require('firebase-functions') const { endpoint } = require('../common') module.exports = { - subscribe: endpoint(config().marketing.cors_origin, require('./subscribe')) + subscribe: endpoint(config().marketing.cors_origin, require('./subscribe')), + share: endpoint(config().app.cors_origin, require('./share')) } diff --git a/functions/sendgrid/share.js b/functions/sendgrid/share.js new file mode 100644 index 0000000..993cca3 --- /dev/null +++ b/functions/sendgrid/share.js @@ -0,0 +1,34 @@ +const axios = require('axios') +const i18n = require('i18next') +const { compileTemplate, snapshotToShare } = require('../common') +const { database } = require('firebase-admin') +const { config } = require('firebase-functions') +const dayjs = require('dayjs') + +module.exports = (req, t) => + i18n.loadNamespaces('app').then(() => + database().ref(`/rooms/${req.body.uuid}`).once('value') + .then(snapshot => + axios.post('https://api.sendgrid.com/v3/mail/send', { + from: { email: "noreply@minimal.cards", name: "Minimum Viable Ceremonies" }, + personalizations: [{ + to: [{ email: req.body.recipients || 'james.kiesel@gmail.com' }], + subject: t('templates.share.title'), + }], + content: [{ + type: "text/html", + value: compileTemplate('share', { + ...snapshotToShare(snapshot, t), + translations: { + ...t('templates.share', { returnObjects: true }), + footer: t('templates.share.footer', { name: (snapshot.toJSON() || {}).name }), + roomUrl: `${config().app.cors_origin}/room/${req.body.uuid}` + } + }) + }] + }, { + headers: { + 'Authorization': `Bearer ${config().sendgrid.api_key}` + } + }).then(() => [200, { status: t('common.messages.200') }]) + .catch(({ request, response }) => [response.status, response.data]))) diff --git a/functions/slack/common.js b/functions/slack/common.js index 9578137..c63b30e 100644 --- a/functions/slack/common.js +++ b/functions/slack/common.js @@ -1,6 +1,6 @@ const crypto = require('crypto') const tsscmp = require('tsscmp') -const { config } = require('firebase-admin') +const { config } = require('firebase-functions') exports.returnMessage = text => { blocks: [{ type: 'section', text: { type: 'mrkdwn', text } }] } diff --git a/functions/slack/index.js b/functions/slack/index.js index 29193da..aa194d0 100644 --- a/functions/slack/index.js +++ b/functions/slack/index.js @@ -3,5 +3,8 @@ const { endpoint } = require('../common') module.exports = { authorize: endpoint(config().marketing.cors_origin, require('./authorize')), - create: endpoint(config().slack.cors_origin, require('./create')) + create: endpoint(config().slack.cors_origin, require('./create')), + share: endpoint(config().app.cors_origin, require('./share')), + join: endpoint(config().app.cors_origin, require('./join')), + list: endpoint(config().app.cors_origin, require('./list')), } diff --git a/functions/slack/join.js b/functions/slack/join.js new file mode 100644 index 0000000..c8087e5 --- /dev/null +++ b/functions/slack/join.js @@ -0,0 +1,15 @@ +const axios = require('axios') +const i18n = require('i18next') +const { config } = require('firebase-functions') + +module.exports = (req, t) => + axios.post('https://slack.com/api/conversations.join', { + channel: req.body.channel + }, { + headers: { 'Authorization': `Bearer ${config().slack.access_token}` } + }).catch(error => [400, t('slack.errors.400', { errors: error })]) + .then(({ data: { ok, error, conversations } }) => + ok + ? [200, { status: t('common.messages.200') }] + : [400, { status: t('slack.errors.400', { errors: error }) }] + ) diff --git a/functions/slack/list.js b/functions/slack/list.js new file mode 100644 index 0000000..b7336a7 --- /dev/null +++ b/functions/slack/list.js @@ -0,0 +1,20 @@ +const axios = require('axios') +const i18n = require('i18next') +const { config } = require('firebase-functions') + +module.exports = (req, t) => + axios.post('https://slack.com/api/conversations.list', {}, { + headers: { 'Authorization': `Bearer ${config().slack.access_token}` } + }).catch(error => [400, t('slack.errors.400', { errors: error })]) + .then(({ data: { ok, error, channels } }) => + ok + ? [200, { channels: transformChannels(channels) }] + : [400, { status: t('slack.errors.400', { errors: error }) }]) + +const transformChannels = channels => + channels + .filter(channel => !channel.is_archived) + .map(channel => ({ + id: channel.id, + name: channel.name, + })) diff --git a/functions/slack/share.js b/functions/slack/share.js new file mode 100644 index 0000000..c7f7e87 --- /dev/null +++ b/functions/slack/share.js @@ -0,0 +1,59 @@ +const axios = require('axios') +const i18n = require('i18next') +const { config } = require('firebase-functions') +const { database } = require('firebase-admin') +const { getAccessToken } = require('./common') +const { snapshotToShare } = require('../common') + +module.exports = (req, t) => + i18n.loadNamespaces('app').then(() => + database().ref(`/rooms/${JSON.parse(req.body).uuid}`).once('value').then(snapshot => { + const { channel, uuid } = JSON.parse(req.body) + const { stats, cadences } = snapshotToShare(snapshot, t) + + return axios.post('https://slack.com/api/chat.postMessage', { + channel, + blocks: [{ + type: 'header', + text: { type: 'plain_text', text: t('templates.share.congrats') } + }, { + type: 'section', + text: { type: 'mrkdwn', text: t('templates.share.encouragement') } + }, { + type: 'divider' + }, { + type: 'header', + text: { type: 'plain_text', text: t('templates.share.stats.title') } + }, { + type: 'section', + fields: [ + { type: 'mrkdwn', text: `• ${stats.ceremonies} ${t('templates.share.stats.ceremonies')}` }, + { type: 'mrkdwn', text: `• ${stats.people} ${t('templates.share.stats.people')}` }, + { type: 'mrkdwn', text: `• ${stats.weekly} ${t('templates.share.stats.weekly')}` } + ] + }, { + type: 'divider' + }, { + type: 'header', + text: { type: 'plain_text', text: t('templates.share.schedule.title') } + }, { + type: 'divider' + }, { + type: 'section', + text: { type: 'plain_text', text: t('templates.share.footer') }, + accessory: { + type: 'button', + text: { type: 'plain_text', text: t('templates.share.backToRoom') }, + url: `${config().app.cors_origin}/room/${uuid}` + } + }] + }, { + headers: { 'Authorization': `Bearer ${config().slack.access_token}` } + }).catch(error => [400, t('slack.errors.400', { errors: error })]) + .then(({ data: { ok, error } }) => + ok + ? [200, { status: t('common.messages.200') }] + : [400, { status: t('slack.errors.400', { errors: error }) }] + ) + }) + ) diff --git a/functions/templates/cadence.hbs b/functions/templates/cadence.hbs new file mode 100644 index 0000000..0b67c03 --- /dev/null +++ b/functions/templates/cadence.hbs @@ -0,0 +1,28 @@ +
+

{{name}}

+ {{#each ceremonies}} +
+
+
{{icon}} {{title}}
+ {{#if pill}} + {{pill}} + {{/if}} +
+ {{#if people}} +
+ {{#each people}} +
{{initial}}
+ {{/each}} +
+ {{/if}} +
+
+ {{#if notes}} + {{notes}} + {{else}} + {{@root.translations.noNotes}} + {{/if}} +
+
+ {{/each}} +
diff --git a/functions/templates/share.hbs b/functions/templates/share.hbs new file mode 100644 index 0000000..777024c --- /dev/null +++ b/functions/templates/share.hbs @@ -0,0 +1,59 @@ + + + {{@root.translations.title}} + + + + {{> shareStyles}} + + +
+
+

🙃  Minimum Viable Ceremonies

+

+ https://minimal.cards +

+
+
+
+
🎉
+

{{@root.translations.congrats}}

+

{{@root.translations.encouragement}}

+
+
+

{{@root.translations.stats.title}}

+
+ {{#each stats}} +
+
{{figure}}
+
{{description}}
+
+ {{/each}} +
+ +

{{@root.translations.schedule.title}}

+ + {{#if cadences.general}} +

{{@root.translations.schedule.sprint}}

+ {{#each cadences.sprint}} + {{> cadence}} + {{/each}} + {{/if}} + + {{#if cadences.sprint}} +

{{@root.translations.schedule.general}}

+ {{#each cadences.general}} + {{> cadence}} + {{/each}} + {{/if}} +
+ +
+
+ + diff --git a/functions/templates/shareStyles.hbs b/functions/templates/shareStyles.hbs new file mode 100644 index 0000000..23b28d2 --- /dev/null +++ b/functions/templates/shareStyles.hbs @@ -0,0 +1,140 @@ + diff --git a/functions/yarn.lock b/functions/yarn.lock index 3d0fa08..05396d6 100644 --- a/functions/yarn.lock +++ b/functions/yarn.lock @@ -590,6 +590,11 @@ date-and-time@^0.13.0: resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-0.13.1.tgz#d12ba07ac840d5b112dc4c83f8a03e8a51f78dd6" integrity sha512-/Uge9DJAT+s+oAcDxtBhyR8+sKjUnZbYmyhbmWjTHNtX7B7oWD8YyYdeXcBRbwSj6hVvj+IQegJam7m7czhbFw== +dayjs@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.9.5.tgz#fd49994ebe71639d2ce9575e97186642dfce9808" + integrity sha512-WULIw7UpW/E0y6VywewpbXAMH3d5cZijEhoHLwM+OMVbk/NtchKS/W+57H/0P1rqU7gHrAArjiRLHCUhgMQl6w== + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -1096,6 +1101,18 @@ gtoken@^4.1.0: jws "^4.0.0" mime "^2.2.0" +handlebars@^4.7.6: + version "4.7.6" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e" + integrity sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -1626,7 +1643,7 @@ moment-timezone@^0.5.31: dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0", moment@^2.27.0: +"moment@>= 2.9.0": version "2.27.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d" integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ== @@ -1651,6 +1668,11 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +neo-async@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + node-environment-flags@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" @@ -2007,6 +2029,11 @@ snakeize@^0.1.0: resolved "https://registry.yarnpkg.com/snakeize/-/snakeize-0.1.0.tgz#10c088d8b58eb076b3229bb5a04e232ce126422d" integrity sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0= +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -2180,6 +2207,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +uglify-js@^3.1.4: + version "3.11.5" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.11.5.tgz#d6788bc83cf35ff18ea78a65763e480803409bc6" + integrity sha512-btvv/baMqe7HxP7zJSF7Uc16h1mSfuuSplT0/qdjxseesDU+yYzH33eHBH+eMdeRXwujXspaCTooWHQVVBh09w== + unique-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -2283,6 +2315,11 @@ wide-align@1.1.3: dependencies: string-width "^1.0.2 || 2" +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"