diff --git a/.dockerignore b/.dockerignore index e413ac5ea..14b1fb97f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,18 +1,3 @@ -# excluded because we'll likely need to rebuild this. node_modules - -# scripts are used during development and testing, not -# production. -scripts - -# documentation should not be visable in production. -docs - -# static assets are rebuild in the docker container. -dist - -# tests are not run in the docker container. test - -# we won't use the .git folder in production. .git diff --git a/.editorconfig b/.editorconfig index 4a7ea3036..986314661 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,11 +2,14 @@ root = true [*] indent_style = space -indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +[{package.json,.*rc,*.yml}] +indent_style = space +indent_size = 2 + [*.md] trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore index 6ec4c0c88..83564d3ff 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,3 @@ dist client/lib **/*.html -plugins/ -plugins/**/node_modules \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5edaabbd1..9ce1637d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,20 @@ node_modules npm-debug.log* dist +!dist/coral-admin +dist/coral-admin/bundle.js test/e2e/reports +.DS_Store +*.iml +*.swp dump.rdb .env *.cfg .idea/ coverage/ -*.swp -plugins.json -plugins -!plugins/coral-plugin-respect +.tags +.tags1 + +# remove plugin folders +# plugins +# plugins.json diff --git a/Dockerfile b/Dockerfile index 5bc38013a..f8cb8a45d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,21 +5,16 @@ RUN mkdir -p /usr/src/app WORKDIR /usr/src/app # Setup the environment +ENV NODE_ENV production ENV PATH /usr/src/app/bin:$PATH ENV TALK_PORT 5000 EXPOSE 5000 +# Install app dependencies +COPY package.json yarn.lock /usr/src/app/ +RUN yarn install --production + # Bundle app source COPY . /usr/src/app -# Install app dependencies and build static assets. -RUN yarn install --frozen-lockfile && \ - cli plugins reconcile && \ - yarn build && \ - yarn install --production && \ - yarn cache clean - -# Ensure the runtime of the container is in production mode. -ENV NODE_ENV production - CMD ["yarn", "start"] diff --git a/Dockerfile.onbuild b/Dockerfile.onbuild deleted file mode 100644 index 34c321448..000000000 --- a/Dockerfile.onbuild +++ /dev/null @@ -1,14 +0,0 @@ -FROM coralproject/talk:latest - -# Bundle app source -ONBUILD COPY . /usr/src/app - -# At this stage, we need to install the development dependancies again because -# we need to have webpack available. We then build the new dependancies and -# clear out the development dependancies again. After this we of course need to -# clear out the yarn cache, this saves quite a lot of size. -ONBUILD RUN NODE_ENV=development yarn install --frozen-lockfile && \ - NODE_ENV=production cli plugins reconcile && \ - NODE_ENV=production yarn build && \ - NODE_ENV=production yarn install --production && \ - yarn cache clean \ No newline at end of file diff --git a/PLUGINS.md b/PLUGINS.md index e5cf8d760..a77b0b8b2 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -9,7 +9,7 @@ All plugins must be registered in the root file `plugins.json`. The format for this file is thus: -```json +```js { "server": [ "people" @@ -22,42 +22,6 @@ name in the `plugins/` folder. For example, the above `plugins.json` would require a plugin from `plugins/people`, which must provide a `index.js` file that returns an object that matches the Plugin Specification. -If the package is external (available on NPM) you can specify the string for -the version by using an object instead, for example: - -```json -{ - "server": [ - {"people": "^1.2.0"} - ] -} -``` - -External plugins can be resolved by running: - -```bash -./bin/cli plugins reconcile -``` - -This will also traverse into local plugin folders and install their -dependancies. _Note that if the plugin is already installed and available in the -node_modules folder, it will not be fetched again unless there is a version -mismatch._ - -## Plugin Dependencies - -From your plugins you may import any component of server code relative to the -project root. An example could be: - -```js -const cache = require('services/cache'); -``` - -You may also include additional external depenancies in your local packages by -specifying a `package.json` at your plugin root which will result in a -`node_modules` folder being generated at the plugin root with your specific -dependencies. - ## Server Plugins ### Specification @@ -206,88 +170,6 @@ If your post function accepts four parameters, then it can modify the field result. It is *required* that the function resolves a promise (or returns) with the modified value or simply the original if you didn't modify it. -#### Field: `router` - -```js -(router) => { - router.get('/api/v1/people', (req, res) => { - res.json({people: [{name: 'Bob'}]}); - }); -} -``` - -The Router hook allows you to create a function that accepts the base express -router where you can mount any amount of middleware/routes to do any form of -action needed by external applications. We also provide the authorization -middleware via: - -```js -const authorization = require('middleware/authorization'); - -module.exports = { - router(router) { - router.get('/api/v1/people', authorization.needed('ADMIN'), (req, res) => { - res.json({people: [{name: 'SECRET PEOPLE'}]}); - }); - } -} -``` - -#### Field: `passport` - -```js -const FacebookStrategy = require('passport-facebook').Strategy; -const UsersService = require('services/users'); -const {ValidateUserLogin, HandleAuthPopupCallback} = require('services/passport'); - -module.exports = { - passport(passport) { - passport.use(new FacebookStrategy({ - clientID: process.env.TALK_FACEBOOK_APP_ID, - clientSecret: process.env.TALK_FACEBOOK_APP_SECRET, - callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`, - passReqToCallback: true, - profileFields: ['id', 'displayName', 'picture.type(large)'] - }, async (req, accessToken, refreshToken, profile, done) => { - - let user; - try { - user = await UsersService.findOrCreateExternalUser(profile); - } catch (err) { - return done(err); - } - - return ValidateUserLogin(profile, user, done); - })); - }, - router(router) { - - // Note that we have to import the passport instance here, it is - // instantiated after all the strategies have been mounted. - const {passport} = require('services/passport'); - - /** - * Facebook auth endpoint, this will redirect the user immediatly to facebook - * for authorization. - */ - router.get('/facebook', passport.authenticate('facebook', {display: 'popup', authType: 'rerequest', scope: ['public_profile']})); - - /** - * Facebook callback endpoint, this will send the user a html page designed to - * send back the user credentials upon sucesfull login. - */ - router.get('/facebook/callback', (req, res, next) => { - - // Perform the facebook login flow and pass the data back through the opener. - passport.authenticate('facebook', HandleAuthPopupCallback(req, res, next))(req, res, next); - }); - } -}; -``` - -This is a full example including the routes hook to add the required components -to the application router to support a different auth strategy. - ### Full Example Contents of `plugins.json`: diff --git a/README.md b/README.md index 3111446ea..7e303f70e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Talk [![CircleCI](https://circleci.com/gh/coralproject/talk.svg?style=svg)](https://circleci.com/gh/coralproject/talk) -Talk is currently in Beta! [Read more about Talk here.](https://coralproject.net/products/talk.html) +A commenting platform from [The Coral Project](https://coralproject.net). Talk enters a closed beta in March 2017, but you can download the code for our alpha here. [Read more about Talk here.](https://coralproject.net/products/talk.html) Third party licenses are available via the `/client/3rdpartylicenses.txt` endpoint when the server is running with built assets. diff --git a/app.js b/app.js index 3ae03ec72..5b704bea0 100644 --- a/app.js +++ b/app.js @@ -3,7 +3,7 @@ const bodyParser = require('body-parser'); const morgan = require('morgan'); const path = require('path'); const helmet = require('helmet'); -const {passport} = require('./services/passport'); +const passport = require('./services/passport'); const session = require('express-session'); const enabled = require('debug').enabled; const RedisStore = require('connect-redis')(session); diff --git a/bin/cli b/bin/cli index 7e27fa85b..c0690e455 100755 --- a/bin/cli +++ b/bin/cli @@ -13,7 +13,6 @@ program .command('setup', 'setup the application') .command('jobs', 'work with the job queues') .command('users', 'work with the application auth') - .command('plugins', 'provides utilities for interacting with the plugin system') .parse(process.argv); /** diff --git a/bin/cli-plugins b/bin/cli-plugins deleted file mode 100755 index 94ce74904..000000000 --- a/bin/cli-plugins +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/env node - -/** - * Module dependencies. - */ - -// Interface heavily inspired by the yarn package manager: -// https://yarnpkg.com/ - -const program = require('./commander'); - -// Make things colorful! -require('colors'); -const emoji = require('node-emoji'); - -const dir = process.cwd(); -const fs = require('fs'); -const path = require('path'); -const spawn = require('cross-spawn'); -const semver = require('semver'); -const resolve = require('resolve'); -const {plugins, itteratePlugins, isInternal} = require('../plugins'); - -function existsInNodeModules(name) { - try { - resolve.sync(name, {basedir: dir}); - - return true; - } catch (e) { - return false; - } -} - -function versionMatch(name, version) { - try { - let matched = false; - - resolve.sync(name, { - basedir: dir, - packageFilter: (pkg) => { - if (pkg && pkg.version && semver.satisfies(pkg.version, version)) { - matched = true; - } - - return pkg; - } - }); - - return matched; - } catch (e) { - return false; - } -} - -const EXTERNAL = /^\w[a-z\-0-9\.]+$/; // Match "react", "path", "fs", "lodash.random", etc. - -function reconcilePackages({quiet = false, upgradeRemote = false}) { - const fetchable = []; - const local = []; - const upgradable = []; - - if (!quiet) { - console.log(); - console.log(' +local (l) packages in your project'); - console.log(' +external (e) packages are external'); - console.log(' +outofdate (oe) packages are external but are out of date'); - console.log(' +missing (m) packages are not found'); - console.log(); - } - - for (let i in plugins) { - let section = itteratePlugins(plugins[i]); - - for (let j in section) { - let {name, version} = section[j]; - - let namespaced = name.charAt(0) === '@'; - let dep = name.split('/') - .slice(0, namespaced ? 2 : 1) - .join('/'); - - // Ignore relative modules, which aren't installed by NPM - if (!dep.match(EXTERNAL) && !namespaced) { - return; - } - - if (isInternal(dep)) { - if (!quiet) { - console.log(` l ${name}`); - } - - local.push({name, version}); - continue; - } - - if (!existsInNodeModules(dep)) { - if (!quiet) { - console.log(` m ${name}`); - } - fetchable.push({name, version}); - } else if (!versionMatch(dep, version)) { - - // A plugin was found, yet the current version does not match the - // current version installed. We should warn if upgradeRemote is - // not enabled that it is currently not supported. - if (!upgradeRemote) { - if (!quiet) { - console.warn(` oe ${name} (package upgrade may be required)`.bgRed); - } - - continue; - } - - console.log(` oe ${name} (package upgrade may be required)`); - - upgradable.push({name, version}); - } else { - if (!quiet) { - console.log(` e ${name}`); - } - - if (upgradeRemote) { - upgradable.push({name, version}); - } - } - } - } - - if (!quiet) { - console.log(); - } - - return {local, fetchable, upgradable}; -} - -async function reconcileRemotePlugins({skipLocal, dryRun, upgradeRemote}) { - console.log(`\n[${skipLocal ? '1/2' : '2/3'}] ${emoji.get('mag')} Reconciling plugins...`.yellow); - const {fetchable, upgradable} = reconcilePackages({upgradeRemote}); - - console.log(`[${skipLocal ? '2/2' : '3/3'}] ${emoji.get('truck')} Fetching plugins...\n`.yellow); - - if (fetchable.length > 0) { - - console.log(`$ yarn add ${fetchable.map(({name, version}) => `${name}@${version}`.cyan)}`); - - if (!dryRun) { - - let args = [ - 'add', - ...fetchable.map(({name, version}) => `${name}@${version}`) - ]; - - let output = spawn.sync('yarn', args, { - stdio: ['ignore', 'pipe', 'inherit'] - }); - - if (output.status) { - throw new Error('Could not install external plugins, errors occured during install'); - } - - console.log(output.stdout.toString()); - } - } - - if (upgradable.length > 0) { - console.log(`$ yarn upgrade ${upgradable.map(({name, version}) => `${name}@${version}`.cyan)}`); - - if (!dryRun) { - - let args = [ - 'upgrade', - ...upgradable.map(({name, version}) => `${name}@${version}`) - ]; - - let output = spawn.sync('yarn', args, { - stdio: ['ignore', 'pipe', 'inherit'] - }); - - if (output.status) { - throw new Error('Could not install external plugins, errors occured during install'); - } - - console.log(output.stdout.toString()); - } - } - - return {upgradable, fetchable}; -} - -async function reconcileLocalPlugins({skipRemote, dryRun}) { - console.log(`\n[${skipRemote ? '1/1' : '1/3'}] ${emoji.get('pick')} Installing local plugin dependencies...\n`.yellow); - const {local} = reconcilePackages({quiet: true}); - - for (let i in local) { - let {name} = local[i]; - - if (!fs.existsSync(path.join(dir, 'plugins', name, 'package.json'))) { - continue; - } - - let wd = path.join(dir, 'plugins', name); - - console.log(`$ cd ${wd.cyan} && yarn`); - - if (!dryRun) { - let args = []; - - let output = spawn.sync('yarn', args, { - stdio: ['ignore', 'pipe', 'inherit'], - cwd: wd - }); - - if (output.status) { - throw new Error('Could not install local plugin dependencies, errors occured during install'); - } - - console.log(output.stdout.toString()); - } - } -} - -// This traverses the local plugins and installs any dependencies listed there, -// this only is really needed for plugins that are installed via docker because -// core plugins will have their dependencies already included in core. -async function reconcilePluginDeps({skipLocal, skipRemote, dryRun, upgradeRemote}) { - let startTime = new Date(); - - // We don't need to do anything if we skip everything.... - if (skipLocal && skipRemote) { - return; - } - - // Traverse local plugins and install dependencies if enabled. - if (!skipLocal) { - await reconcileLocalPlugins({skipRemote, dryRun}); - } - - // Locate any external plugins and install them. - if (!skipRemote) { - let results = []; - try { - results = await reconcileRemotePlugins({skipLocal, skipRemote, dryRun, upgradeRemote}); - } catch (e) { - throw e; - } - - let status; - if (dryRun) { - status = '[dry-run] success'.green; - } else { - status = 'success'.green; - } - - let message; - if (results.upgradable.length === 0 && results.fetchable.length === 0) { - message = 'Already up-to-date.'; - } else if (results.upgradable.length === 0) { - message = `Fetched ${results.fetchable.length} new plugins.`; - } else if (results.fetchable.length === 0) { - message = `Upgraded ${results.upgradable.length} new plugins.`; - } else { - message = `Fetched ${results.fetchable.length} new plugins, upgraded ${results.upgradable.length} plugins.`; - } - - console.log(`\n${status} ${message}`); - } - - let endTime = new Date(); - - let totalTime = ((endTime.getTime() - startTime.getTime()) / 1000).toFixed(2); - console.log(`✨ Done in ${totalTime}s.`); -} - -//============================================================================== -// Setting up the program command line arguments. -//============================================================================== - -program - .command('list') - .description('') - .action(reconcilePackages); - -program - .command('reconcile') - .description('reconciles local plugin dependencies and downloads external plugins') - .option('-u, --upgrade-remote', 'upgrades remote dependencies') - .option('-d, --dry-run', 'does not actually change anything on the filesystem acts only as a simulation') - .option('--skip-local', 'skips the local dependancy reconciliation') - .option('--skip-remote', 'skips the remote plugin reconciliation') - .action(reconcilePluginDeps); - -program.parse(process.argv); - -// If there is no command listed, output help. -if (!process.argv.slice(2).length) { - program.outputHelp(); -} diff --git a/circle.yml b/circle.yml index 013ef28b8..2357ecf4c 100644 --- a/circle.yml +++ b/circle.yml @@ -1,6 +1,6 @@ machine: node: - version: 7 + version: 7.6 services: - docker - redis @@ -20,7 +20,6 @@ dependencies: # - sudo service mongod restart # Install node dependencies. - - yarn --version - yarn cache_directories: - ~/.cache/yarn @@ -48,9 +47,9 @@ deployment: release: tag: /v[0-9]+(\.[0-9]+)*/ commands: - - bash ./scripts/docker.sh deploy + - bash ./scripts/deploy.sh latest: branch: master commands: - - bash ./scripts/docker.sh deploy + - bash ./scripts/deploy.sh diff --git a/client/coral-admin/README.md b/client/coral-admin/README.md new file mode 100644 index 000000000..7bc9de36e --- /dev/null +++ b/client/coral-admin/README.md @@ -0,0 +1,23 @@ + +# Coral Admin + +This app handles moderation for Talk (and maybe more later on) + +## Installation + + $ yarn install + $ cp config.sample.json config.json + +Then change `config.json` to adjust it to your project + +## Building for production + + $ yarn build + +The public folder has everything you need for deployment. You can just copy that folder to your favorite static web server + +## Development + + $ yarn start + +A development server will be running at http://localhost:4132 diff --git a/client/coral-embed-stream/src/Comment.css b/client/coral-embed-stream/src/Comment.css index 4383cbc5b..01022ae66 100644 --- a/client/coral-embed-stream/src/Comment.css +++ b/client/coral-embed-stream/src/Comment.css @@ -5,7 +5,6 @@ .Comment { margin-bottom: 15px; - position: relative; } .pendingComment { diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index f5781a430..39c7d908f 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -148,7 +148,8 @@ class Comment extends React.Component { id={`c_${comment.id}`} style={{marginLeft: depth * 30}}>
-
+ +
{ isStaff(comment.tags) @@ -191,6 +192,7 @@ class Comment extends React.Component { removeBest={removeBestTag} /> +
@@ -244,7 +246,8 @@ class Comment extends React.Component { showSignInDialog={showSignInDialog} reactKey={reply.id} key={reply.id} - comment={reply} />; + comment={reply} + />; }) } { diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index fa59cc1db..31f829829 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -6,17 +6,16 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-framework/translations'; const lang = new I18n(translations); -import {TabBar, Tab, TabContent, Spinner, Button} from 'coral-ui'; +import {TabBar, Tab, TabContent, Spinner} from 'coral-ui'; const {logout, showSignInDialog, requestConfirmEmail} = authActions; const {addNotification, clearNotification} = notificationActions; const {fetchAssetSuccess} = assetActions; -import {NEW_COMMENT_COUNT_POLL_INTERVAL} from 'coral-framework/constants/comments'; import {queryStream} from 'coral-framework/graphql/queries'; import {postComment, postFlag, postLike, postDontAgree, deleteAction, addCommentTag, removeCommentTag} from 'coral-framework/graphql/mutations'; import {editName} from 'coral-framework/actions/user'; -import {updateCountCache, viewAllComments} from 'coral-framework/actions/asset'; +import {updateCountCache} from 'coral-framework/actions/asset'; import {notificationActions, authActions, assetActions, pym} from 'coral-framework'; import Stream from './Stream'; @@ -32,7 +31,7 @@ import ChangeUsernameContainer from '../../coral-sign-in/containers/ChangeUserna import ProfileContainer from 'coral-settings/containers/ProfileContainer'; import RestrictedContent from 'coral-framework/components/RestrictedContent'; import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer'; -import HighlightedComment from './Comment'; +import Comment from './Comment'; import LoadMore from './LoadMore'; import NewCount from './NewCount'; @@ -70,30 +69,10 @@ class Embed extends Component { pym.sendMessage('childReady'); } - componentWillUnmount () { - clearInterval(this.state.countPoll); - } - componentWillReceiveProps (nextProps) { const {loadAsset} = this.props; if(!isEqual(nextProps.data.asset, this.props.data.asset)) { loadAsset(nextProps.data.asset); - - const {getCounts, updateCountCache} = this.props; - const {asset} = nextProps.data; - - updateCountCache(asset.id, asset.commentCount); - - this.setState({ - countPoll: setInterval(() => { - const {asset} = this.props.data; - getCounts({ - asset_id: asset.id, - limit: asset.comments.length, - sort: 'REVERSE_CHRONOLOGICAL' - }); - }, NEW_COMMENT_COUNT_POLL_INTERVAL) - }); } } @@ -101,7 +80,7 @@ class Embed extends Component { if(!isEqual(prevProps.data.comment, this.props.data.comment)) { // Scroll to a permalinked comment if one is in the URL once the page is done rendering. - setTimeout(() => pym.scrollParentToChildEl('coralStream'), 0); + setTimeout(()=>pym.scrollParentToChildEl(`c_${this.props.data.comment.id}`), 0); } } @@ -119,8 +98,6 @@ class Embed extends Component { const {closedAt, countCache = {}} = this.props.asset; const {loading, asset, refetch, comment} = this.props.data; const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth; - - // even though the permalinked comment is the highlighted one, we're displaying its parent + replies const highlightedComment = comment && comment.parent ? comment.parent : comment; const openStream = closedAt === null; @@ -148,16 +125,6 @@ class Embed extends Component { {lang.t('MY_COMMENTS')} Configure Stream - { - highlightedComment && - - } {loggedIn && this.props.logout().then(refetch)} changeTab={this.changeTab}/>} { @@ -204,11 +171,9 @@ class Embed extends Component { offset={signInOffset}/>} {loggedIn && user && } {loggedIn && } - - {/* the highlightedComment is isolated after the user followed a permalink */} { - highlightedComment - ? asset.comments.length} loadMore={this.props.loadMore}/> - comment={highlightedComment} /> - :
- -
- -
- asset.comments.length} - loadMore={this.props.loadMore} /> -
- }
({ editName: (username) => dispatch(editName(username)), showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), updateCountCache: (id, count) => dispatch(updateCountCache(id, count)), - viewAllComments: () => dispatch(viewAllComments()), logout: () => dispatch(logout()), dispatch: d => dispatch(d) }); @@ -352,5 +279,5 @@ export default compose( addCommentTag, removeCommentTag, deleteAction, - queryStream + queryStream, )(Embed); diff --git a/client/coral-embed-stream/src/LoadMore.js b/client/coral-embed-stream/src/LoadMore.js index 1a58c8d19..89dc0104c 100644 --- a/client/coral-embed-stream/src/LoadMore.js +++ b/client/coral-embed-stream/src/LoadMore.js @@ -5,7 +5,7 @@ import {ADDTL_COMMENTS_ON_LOAD_MORE} from 'coral-framework/constants/comments'; import {Button} from 'coral-ui'; const lang = new I18n(translations); -const loadMoreComments = (assetId, comments, loadMore, parentId, replyCount) => { +const loadMoreComments = (assetId, comments, loadMore, parentId) => { let cursor = null; if (comments.length) { @@ -15,7 +15,7 @@ const loadMoreComments = (assetId, comments, loadMore, parentId, replyCount) => } loadMore({ - limit: parentId ? replyCount : ADDTL_COMMENTS_ON_LOAD_MORE, + limit: ADDTL_COMMENTS_ON_LOAD_MORE, cursor, asset_id: assetId, parent_id: parentId, @@ -48,7 +48,7 @@ class LoadMore extends React.Component { diff --git a/client/coral-embed-stream/src/Stream.js b/client/coral-embed-stream/src/Stream.js index 66c19e0ab..37f26218a 100644 --- a/client/coral-embed-stream/src/Stream.js +++ b/client/coral-embed-stream/src/Stream.js @@ -1,5 +1,6 @@ import React, {PropTypes} from 'react'; import Comment from './Comment'; +import {NEW_COMMENT_COUNT_POLL_INTERVAL} from 'coral-framework/constants/comments'; class Stream extends React.Component { @@ -26,6 +27,26 @@ class Stream extends React.Component { this.state = {activeReplyBox: '', countPoll: null}; } + componentDidMount() { + const {asset, getCounts, updateCountCache} = this.props; + + updateCountCache(asset.id, asset.commentCount); + + // Note: Apollo's built-in polling doesn't work with fetchMore queries, so a + // setInterval is being used instead. + this.setState({ + countPoll: setInterval(() => getCounts({ + asset_id: asset.id, + limit: asset.comments.length, + sort: 'REVERSE_CHRONOLOGICAL' + }), NEW_COMMENT_COUNT_POLL_INTERVAL), + }); + } + + componentWillUnmount() { + clearInterval(this.state.countPoll); + } + render () { const { comments, diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 6a6a2214d..599925758 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -16,7 +16,6 @@ body { font-size: 14px; margin: 0px; padding: 0px 0px 50px 0px; - height: auto !important; } .expandForSignin { @@ -307,7 +306,10 @@ button.comment__action-button[disabled], display: none; background-color: white; border: 1px solid black; + width: calc(100% - 15px); position: absolute; + top: 70px; + right: 0; padding: 5px; } diff --git a/client/coral-embed/src/index.js b/client/coral-embed/src/index.js index e3350e0d3..8ebd403b8 100644 --- a/client/coral-embed/src/index.js +++ b/client/coral-embed/src/index.js @@ -74,15 +74,6 @@ function configurePymParent(pymParent, asset_url) { snackbar.style.opacity = 0; }); - // remove the permalink comment id from the hash - pymParent.onMessage('coral-view-all-comments', function () { - window.history.replaceState( - {}, - document.title, - location.origin + location.pathname + location.search - ); - }); - pymParent.onMessage('coral-alert', function (message) { const [type, text] = message.split('|'); snackbar.style.transform = 'translate(-50%, 20px)'; diff --git a/client/coral-framework/actions/asset.js b/client/coral-framework/actions/asset.js index 50efa3dcb..02e72ea98 100644 --- a/client/coral-framework/actions/asset.js +++ b/client/coral-framework/actions/asset.js @@ -1,7 +1,6 @@ import * as actions from '../constants/asset'; import coralApi from '../helpers/response'; import {addNotification} from '../actions/notification'; -import {pym} from 'coral-framework'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from './../translations'; @@ -50,37 +49,3 @@ export const updateOpenStatus = status => dispatch => { dispatch(updateOpenStream({closedAt: new Date().getTime()})); } }; - -function removeParam(key, sourceURL) { - let rtn = sourceURL.split('?')[0]; - let param; - let params_arr = []; - let queryString = (sourceURL.indexOf('?') !== -1) ? sourceURL.split('?')[1] : ''; - if (queryString !== '') { - params_arr = queryString.split('&'); - for (let i = params_arr.length - 1; i >= 0; i -= 1) { - param = params_arr[i].split('=')[0]; - if (param === key) { - params_arr.splice(i, 1); - } - } - rtn = `${rtn}?${params_arr.join('&')}`; - } - return rtn; -} - -export const viewAllComments = () => { - - // remove the comment_id url param - const modifiedUrl = removeParam('comment_id', location.href); - try { - - // "window" here refers to the embedded iframe - window.history.replaceState({}, document.title, modifiedUrl); - - // also change the parent url - pym.sendMessage('coral-view-all-comments'); - } catch (e) { /* not sure if we're worried about old browsers */ } - - return {type: actions.VIEW_ALL_COMMENTS}; -}; diff --git a/client/coral-framework/constants/asset.js b/client/coral-framework/constants/asset.js index 5547c5284..234095d9d 100644 --- a/client/coral-framework/constants/asset.js +++ b/client/coral-framework/constants/asset.js @@ -9,5 +9,3 @@ export const UPDATE_ASSET_SETTINGS_FAILURE = 'UPDATE_ASSET_SETTINGS_FAILURE'; export const OPEN_COMMENTS = 'OPEN_COMMENTS'; export const CLOSE_COMMENTS = 'CLOSE_COMMENTS'; export const UPDATE_COUNT_CACHE = 'UPDATE_COUNT_CACHE'; - -export const VIEW_ALL_COMMENTS = 'VIEW_ALL_COMMENTS'; diff --git a/client/coral-framework/graphql/queries/index.js b/client/coral-framework/graphql/queries/index.js index ec0dcc336..cdf2356d8 100644 --- a/client/coral-framework/graphql/queries/index.js +++ b/client/coral-framework/graphql/queries/index.js @@ -5,7 +5,6 @@ import GET_COUNTS from './getCounts.graphql'; import MY_COMMENT_HISTORY from './myCommentHistory.graphql'; import uniqBy from 'lodash/uniqBy'; import sortBy from 'lodash/sortBy'; -import isNil from 'lodash/isNil'; function getQueryVariable(variable) { let query = window.location.search.substring(1); @@ -21,7 +20,6 @@ function getQueryVariable(variable) { return null; } -// get the counts of the top-level comments export const getCounts = (data) => ({asset_id, limit, sort}) => { return data.fetchMore({ query: GET_COUNTS, @@ -43,48 +41,23 @@ export const getCounts = (data) => ({asset_id, limit, sort}) => { }); }; -// handle paginated requests for more Comments pertaining to the Asset export const loadMore = (data) => ({limit, cursor, parent_id = null, asset_id, sort}, newComments) => { return data.fetchMore({ query: LOAD_MORE, variables: { - limit, // how many comments are we returning - cursor, // the date of the first/last comment depending on the sort order - parent_id, // if null, we're loading more top-level comments, if not, we're loading more replies to a comment - asset_id, // the id of the asset we're currently on - sort // CHRONOLOGICAL or REVERSE_CHRONOLOGICAL + limit, + cursor, + parent_id, + asset_id, + sort }, updateQuery: (oldData, {fetchMoreResult:{data:{new_top_level_comments}}}) => { let updatedAsset; - if (!isNil(oldData.comment)) { // loaded replies on a highlighted (permalinked) comment - - let comment = {}; - if (oldData.comment && oldData.comment.parent) { - - // put comments (replies) onto the oldData.comment.parent object - // the initial comment permalinked was a reply - const uniqReplies = uniqBy([...new_top_level_comments, ...oldData.comment.parent.replies], 'id'); - comment.parent = {...oldData.comment.parent, replies: sortBy(uniqReplies, 'created_at')}; - } else if (oldData.comment) { - - // put the comments (replies) directly onto oldData.comment - // the initial comment permalinked was a top-level comment - const uniqReplies = uniqBy([...new_top_level_comments, ...oldData.comment.replies], 'id'); - comment.replies = sortBy(uniqReplies, 'created_at'); - } - - updatedAsset = { - ...oldData, - comment: { - ...oldData.comment, - ...comment - } - }; - - } else if (parent_id) { // If loading more replies + if (parent_id) { + // If loading more replies updatedAsset = { ...oldData, asset: { @@ -103,8 +76,9 @@ export const loadMore = (data) => ({limit, cursor, parent_id = null, asset_id, s }) } }; - } else { // If loading more top-level comments + } else { + // If loading more top-level comments updatedAsset = { ...oldData, asset: { @@ -120,11 +94,8 @@ export const loadMore = (data) => ({limit, cursor, parent_id = null, asset_id, s }); }; -// load the comment stream. export const queryStream = graphql(STREAM_QUERY, { options: () => { - - // where the query string is from the embeded iframe url let comment_id = getQueryVariable('comment_id'); let has_comment = comment_id != null; diff --git a/client/coral-framework/graphql/queries/streamQuery.graphql b/client/coral-framework/graphql/queries/streamQuery.graphql index c9f9d3d5b..03af1de4c 100644 --- a/client/coral-framework/graphql/queries/streamQuery.graphql +++ b/client/coral-framework/graphql/queries/streamQuery.graphql @@ -1,18 +1,10 @@ #import "../fragments/commentView.graphql" query AssetQuery($asset_id: ID, $asset_url: String!, $comment_id: ID!, $has_comment: Boolean!) { - # the comment here is for loading one comment and it's children, probably after following a permalink - # $has_comment is derived from the comment_id query param in the iframe url, - # which is in turn pulled from the host page url comment(id: $comment_id) @include(if: $has_comment) { ...commentView - replyCount - replies { - ...commentView - } parent { ...commentView - replyCount replies { ...commentView } diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index 31c3e98ea..f9038aba7 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -13,7 +13,6 @@ "error": "Usernames can contain letters, numbers and _ only" }, "viewMoreComments": "view more comments", - "showAllComments": "Show all comments", "viewReply": "view reply", "viewAllRepliesInitial": "view all {0} replies", "viewAllReplies": "view {0} replies", @@ -57,7 +56,6 @@ "newCount": "Ver {0} {1} más", "comment": "commentario", "comments": "commentarios", - "showAllComments": "Mostrar todos los comentarios", "error": { "emailNotVerified": "E-mail {0} no verificado.", "email": "No es un e-mail válido", diff --git a/client/coral-plugin-permalinks/PermalinkButton.js b/client/coral-plugin-permalinks/PermalinkButton.js index 464155b2e..4c6cbb571 100644 --- a/client/coral-plugin-permalinks/PermalinkButton.js +++ b/client/coral-plugin-permalinks/PermalinkButton.js @@ -23,10 +23,6 @@ class PermalinkButton extends React.Component { } toggle () { - - // I wish I could position this with a stylesheet, but top-level comments with - // nested replies throws everything off, as well as very long comments - this.popover.style.top = `${this.linkButton.offsetTop - 80}px`; this.setState({popoverOpen: !this.state.popoverOpen}); } @@ -52,16 +48,11 @@ class PermalinkButton extends React.Component { const {copySuccessful, copyFailure} = this.state; return (
- -
this.popover = ref} - className={`${name}-popover ${styles.container} ${this.state.popoverOpen ? 'active' : ''}`}> +
{ - let plugin = {}; - - // This checks to see if the structure for this entry is an object: - // - // {"people": "^1.2.0"} - // - // otherwise it's checked whether it matches the local version: - // - // "people" - // - if (typeof p === 'object') { - plugin.name = Object.keys(p).find((name) => name !== null); - plugin.version = p[plugin.name]; - } else if (typeof p === 'string') { - plugin.name = p; - plugin.version = `file:./plugins/${plugin.name}`; - } else { - throw new Error(`plugins.json is malformed, refer to PLUGINS.md for formatting, expected a string or an object for a plugin entry, found a ${typeof p}`); - } - - // Get the path for the plugin. - plugin.path = pluginPath(plugin.name); - - return plugin; - }); -} - /** * Stores a reference to a section for a section of Plugins. */ class PluginSection { - constructor(plugins) { - this.plugins = itteratePlugins(plugins).map((plugin) => { - if (typeof plugin.path === 'undefined') { - throw new Error(`plugin '${plugin.name}' is not local and is not resolvable, plugin reconsiliation may be required`); - } + constructor(plugin_names) { + this.plugins = plugin_names.map((plugin_name) => { + let plugin = require(`./plugins/${plugin_name}`); - try { - plugin.module = require(plugin.path); - } catch (e) { - if (e && e.code && e.code === 'MODULE_NOT_FOUND' && isInternal(plugin.name)) { - console.error(new Error(`plugin '${plugin.name}' could not be loaded due to missing dependencies, plugin reconsiliation may be required`)); - throw e; - } - - console.error(new Error(`plugin '${plugin.name}' could not be required from '${plugin.path}': ${e.message}`)); - throw e; - } - - if (isInternal(plugin.name)) { - debug(`loading internal plugin '${plugin.name}' from '${plugin.path}'`); - } else { - debug(`loading external plugin '${plugin.name}' from '${plugin.path}'`); - } + // Ensure we have a default plugin name, but allow the name to be + // overrided by the plugin. + plugin.name = plugin.name || plugin_name; return plugin; }); @@ -121,11 +38,8 @@ class PluginSection { */ hook(hook) { return this.plugins - .filter(({module}) => hook in module) - .map((plugin) => ({ - plugin, - [hook]: plugin.module[hook] - })); + .filter((plugin) => hook in plugin) + .map((plugin) => ({plugin, [hook]: plugin[hook]})); } } @@ -164,10 +78,4 @@ class PluginManager { } } -module.exports = { - plugins, - PluginManager, - isInternal, - pluginPath, - itteratePlugins -}; +module.exports = new PluginManager(plugins); diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js index 12a3e8bbe..fc3b87e51 100644 --- a/routes/api/auth/index.js +++ b/routes/api/auth/index.js @@ -1,6 +1,7 @@ const express = require('express'); -const {passport, HandleAuthCallback, HandleAuthPopupCallback} = require('../../../services/passport'); +const passport = require('../../../services/passport'); const authorization = require('../../../middleware/authorization'); +const errors = require('../../../errors'); const router = express.Router(); @@ -33,6 +34,53 @@ router.delete('/', authorization.needed(), (req, res) => { // PASSPORT ROUTES //============================================================================== +/** + * This sends back the user data as JSON. + */ +const HandleAuthCallback = (req, res, next) => (err, user) => { + if (err) { + return next(err); + } + + if (!user) { + return next(errors.ErrNotAuthorized); + } + + // Perform the login of the user! + req.logIn(user, (err) => { + if (err) { + return next(err); + } + + // We logged in the user! Let's send back the user data and the CSRF token. + res.json({user}); + }); +}; + +/** + * Returns the response to the login attempt via a popup callback with some JS. + */ + +const HandleAuthPopupCallback = (req, res, next) => (err, user) => { + if (err) { + return res.render('auth-callback', {err: JSON.stringify(err), data: null}); + } + + if (!user) { + return res.render('auth-callback', {err: JSON.stringify(errors.ErrNotAuthorized), data: null}); + } + + // Perform the login of the user! + req.logIn(user, (err) => { + if (err) { + return res.render('auth-callback', {err: JSON.stringify(err), data: null}); + } + + // We logged in the user! Let's send back the user data. + res.render('auth-callback', {err: null, data: JSON.stringify(user)}); + }); +}; + /** * Local auth endpoint, will recieve a email and password */ diff --git a/routes/index.js b/routes/index.js index 9e38097f9..58c8f13d7 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,7 +1,5 @@ const express = require('express'); const path = require('path'); -const plugins = require('../services/plugins'); -const debug = require('debug')('talk:routes'); const router = express.Router(); @@ -24,12 +22,4 @@ if (process.env.NODE_ENV !== 'production') { }); } -// Inject server route plugins. -plugins.get('server', 'router').forEach((plugin) => { - debug(`added plugin '${plugin.plugin.name}'`); - - // Pass the root router to the plugin to mount it's routes. - plugin.router(router); -}); - module.exports = router; diff --git a/scripts/docker.sh b/scripts/deploy.sh old mode 100755 new mode 100644 similarity index 57% rename from scripts/docker.sh rename to scripts/deploy.sh index 3aae8faf1..fd681fc7f --- a/scripts/docker.sh +++ b/scripts/deploy.sh @@ -2,7 +2,9 @@ set -e -# Inspired by https://segment.com/blog/ci-at-segment/ +docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS + +# Sourced from https://segment.com/blog/ci-at-segment/ deploy_tag() { # Find our individual versions from the tags @@ -26,7 +28,6 @@ deploy_tag() { do echo "==> tagging $version" docker tag coralproject/talk:latest coralproject/talk:$version - docker tag coralproject/talk:latest-onbuild coralproject/talk:$version-onbuild done # Push each of the tags to docker hub, including latest @@ -34,35 +35,21 @@ deploy_tag() { do echo "==> pushing $version" docker push coralproject/talk:$version - docker push coralproject/talk:$version-onbuild done } deploy_latest() { echo "==> pushing latest" docker push coralproject/talk:latest - docker push coralproject/talk:latest-onbuild } -# build the repo, including the onbuild tagged versions. -docker build -t coralproject/talk:latest -f Dockerfile . -docker build -t coralproject/talk:latest-onbuild -f Dockerfile.onbuild . +# build the repo +docker build -t coralproject/talk . -if [ "$1" = "deploy" ] +# deploy based on the env +if [ -n "$CIRCLE_TAG" ] then - - if [[ -n "$DOCKER_EMAIL" && -n "$DOCKER_USER" && -n "$DOCKER_PASS" ]] - then - - # Log the Docker Daemon in - docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS - fi - - # deploy based on the env - if [ -n "$CIRCLE_TAG" ] - then - deploy_tag - else - deploy_latest - fi -fi \ No newline at end of file + deploy_tag +else + deploy_latest +fi diff --git a/services/passport.js b/services/passport.js index 09ded01e8..f1994392f 100644 --- a/services/passport.js +++ b/services/passport.js @@ -7,7 +7,6 @@ const LocalStrategy = require('passport-local').Strategy; const FacebookStrategy = require('passport-facebook').Strategy; const errors = require('../errors'); const debug = require('debug')('talk:passport'); -const plugins = require('./plugins'); //============================================================================== // SESSION SERIALIZATION @@ -28,52 +27,6 @@ passport.deserializeUser((id, done) => { }); }); -/** - * This sends back the user data as JSON. - */ -const HandleAuthCallback = (req, res, next) => (err, user) => { - if (err) { - return next(err); - } - - if (!user) { - return next(errors.ErrNotAuthorized); - } - - // Perform the login of the user! - req.logIn(user, (err) => { - if (err) { - return next(err); - } - - // We logged in the user! Let's send back the user data and the CSRF token. - res.json({user}); - }); -}; - -/** - * Returns the response to the login attempt via a popup callback with some JS. - */ -const HandleAuthPopupCallback = (req, res, next) => (err, user) => { - if (err) { - return res.render('auth-callback', {err: JSON.stringify(err), data: null}); - } - - if (!user) { - return res.render('auth-callback', {err: JSON.stringify(errors.ErrNotAuthorized), data: null}); - } - - // Perform the login of the user! - req.logIn(user, (err) => { - if (err) { - return res.render('auth-callback', {err: JSON.stringify(err), data: null}); - } - - // We logged in the user! Let's send back the user data. - res.render('auth-callback', {err: null, data: JSON.stringify(user)}); - }); -}; - /** * Validates that a user is allowed to login. * @param {User} user the user to be validated @@ -376,19 +329,4 @@ if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET && console.error('Facebook cannot be enabled, missing one of TALK_FACEBOOK_APP_ID, TALK_FACEBOOK_APP_SECRET, TALK_ROOT_URL'); } -// Inject server route plugins. -plugins.get('server', 'passport').forEach((plugin) => { - debug(`added plugin '${plugin.plugin.name}'`); - - // Pass the passport.js instance to the plugin to allow it to inject it's - // functionality. - plugin.passport(passport); -}); - -module.exports = { - passport, - ValidateUserLogin, - HandleFailedAttempt, - HandleAuthCallback, - HandleAuthPopupCallback -}; +module.exports = passport; diff --git a/services/plugins.js b/services/plugins.js deleted file mode 100644 index ced4bd75a..000000000 --- a/services/plugins.js +++ /dev/null @@ -1,3 +0,0 @@ -const {plugins, PluginManager} = require('../plugins'); - -module.exports = new PluginManager(plugins); diff --git a/yarn.lock b/yarn.lock index 38977f5ce..6c77f3e17 100644 --- a/yarn.lock +++ b/yarn.lock @@ -194,10 +194,6 @@ apollo-client@^1.0.0, apollo-client@^1.0.0-rc.9: "@types/graphql" "^0.9.0" "@types/isomorphic-fetch" "0.0.33" -app-module-path@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5" - "apparatus@>= 0.0.9": version "0.0.9" resolved "https://registry.yarnpkg.com/apparatus/-/apparatus-0.0.9.tgz#37dcd25834ad0b651076596291db823eeb1908bd" @@ -1719,7 +1715,7 @@ colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" -colors@1.1.2, colors@^1.1.2, colors@~1.1.2: +colors@1.1.2, colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" @@ -1967,14 +1963,6 @@ create-hmac@^1.1.0, create-hmac@^1.1.2: create-hash "^1.1.0" inherits "^2.0.1" -cross-spawn@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" @@ -4877,13 +4865,6 @@ lru-cache@2, lru-cache@^2.5.0: version "2.7.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" -lru-cache@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e" - dependencies: - pseudomap "^1.0.1" - yallist "^2.0.0" - lru-cache@~2.6.5: version "2.6.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5" @@ -5272,12 +5253,6 @@ node-dir@^0.1.10: dependencies: minimatch "^3.0.2" -node-emoji@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.5.1.tgz#fd918e412769bf8c448051238233840b2aff16a1" - dependencies: - string.prototype.codepointat "^0.2.0" - node-fetch@^1.0.1, node-fetch@^1.3.3, node-fetch@^1.6.3: version "1.6.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04" @@ -6398,10 +6373,6 @@ ps-tree@^1.0.1: dependencies: event-stream "~3.3.0" -pseudomap@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - public-encrypt@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6" @@ -7043,11 +7014,9 @@ resolve-from@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.2.tgz#1f0442c9e0cbb8136e87b9305f932f46c7f28235" - dependencies: - path-parse "^1.0.5" +resolve@^1.1.6, resolve@^1.1.7: + version "1.2.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.2.0.tgz#9589c3f2f6149d1417a40becc1663db6ec6bc26c" restore-cursor@^1.0.1: version "1.0.1" @@ -7214,16 +7183,6 @@ sha.js@^2.3.6: dependencies: inherits "^2.0.1" -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - shelljs@0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.0.tgz#ce1ed837b4b0e55b5ec3dab84251ab9dbdc0c7ec" @@ -7492,10 +7451,6 @@ string-width@^2.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^3.0.0" -string.prototype.codepointat@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz#6b26e9bd3afcaa7be3b4269b526de1b82000ac78" - string_decoder@^0.10.25, string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -8126,7 +8081,7 @@ which@1.1.1: dependencies: is-absolute "^0.1.7" -which@^1.1.1, which@^1.2.9: +which@^1.1.1: version "1.2.12" resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192" dependencies: @@ -8228,10 +8183,6 @@ y18n@^3.2.0, y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" -yallist@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - yargs-parser@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4"