diff --git a/.gitignore b/.gitignore index 567e64a617..ecc46a9419 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,7 @@ api/logs/* dist/ dump.rdb node_modules/ -node_modules -npm-debug.log +npm-debug.log* pids/ static/logos/*.* storage/*.* diff --git a/api/src/auth/jwt.js b/api/src/auth/jwt.js index 8553dd5295..83c240cd24 100644 --- a/api/src/auth/jwt.js +++ b/api/src/auth/jwt.js @@ -92,6 +92,10 @@ const createOrgJWT = async (user, organisationId, provider) => { }; const createDashboardTokenPayload = async (dashboard, shareableId, provider) => { + if (!shareableId && dashboard.shareable.length > 0) { + shareableId = dashboard.shareable[0]._id; + } + const visualisationIds = getVisualisationIdsFromDashboard(dashboard); return payloadDefaults({ provider, diff --git a/api/src/routes/HttpRoutes.js b/api/src/routes/HttpRoutes.js index a64bf64bce..d390390072 100644 --- a/api/src/routes/HttpRoutes.js +++ b/api/src/routes/HttpRoutes.js @@ -3,6 +3,11 @@ import express from 'express'; import restify from 'express-restify-mongoose'; import git from 'git-rev'; import Promise from 'bluebird'; +import { omit, findIndex } from 'lodash'; +import getAuthFromRequest from 'lib/helpers/getAuthFromRequest'; +import getScopesFromRequest from 'lib/services/auth/authInfoSelectors/getScopesFromAuthInfo'; +import { SITE_ADMIN } from 'lib/constants/scopes'; +import getUserIdFromAuthInfo from 'lib/services/auth/authInfoSelectors/getUserIdFromAuthInfo'; import { jsonSuccess, serverError } from 'api/utils/responses'; import passport from 'api/auth/passport'; import { @@ -199,7 +204,22 @@ restify.serve(router, Export); restify.serve(router, Download); restify.serve(router, Query); restify.serve(router, ImportCsv); -restify.serve(router, User); +restify.serve(router, User, { + preUpdate: (req, res, next) => { + const authInfo = getAuthFromRequest(req); + const scopes = getScopesFromRequest(authInfo); + + if ( + findIndex(scopes, item => item === SITE_ADMIN) < 0 && + (req.body._id !== getUserIdFromAuthInfo(authInfo).toString()) + ) { + // Don't allow changing of passwords + req.body = omit(req.body, 'password'); + } + + next(); + } +}); restify.serve(router, Client); restify.serve(router, Visualisation); restify.serve(router, Dashboard); diff --git a/api/src/routes/tests/http-test.js b/api/src/routes/tests/http-test.js index f1da53edf6..3a9c706eb7 100644 --- a/api/src/routes/tests/http-test.js +++ b/api/src/routes/tests/http-test.js @@ -18,12 +18,11 @@ let jwtToken; let orgJwtToken; const provider = 'native'; -describe('API HTTP Route tests', () => { +describe('API HTTP Route tests', function describeTest() { + this.timeout(10000); before((done) => { - console.log('readyState', connection.readyState); if (connection.readyState !== 1) { connection.on('connected', () => { - console.log('connected'); done(); }); } else { diff --git a/cli/src/commands/bulkInsert.js b/cli/src/commands/bulkInsert.js index 1925d82299..72df47f756 100644 --- a/cli/src/commands/bulkInsert.js +++ b/cli/src/commands/bulkInsert.js @@ -6,7 +6,7 @@ import moment from 'moment'; import hash from 'object-hash'; import uuid from 'uuid'; -TinCan.DEBUG = false; +TinCan.DEBUG = true; let lrs; let _batchSize; diff --git a/cli/src/commands/v2-migrations/20180320093000_shareable_dashboards.js b/cli/src/commands/v2-migrations/20180320093000_shareable_dashboards.js new file mode 100644 index 0000000000..47d79f39d7 --- /dev/null +++ b/cli/src/commands/v2-migrations/20180320093000_shareable_dashboards.js @@ -0,0 +1,75 @@ +import logger from 'lib/logger'; +import mongoose from 'mongoose'; +import { getConnection } from 'lib/connections/mongoose'; +import Dashboard, { schema } from 'lib/models/dashboard'; + +import { map } from 'lodash'; + +const objectId = mongoose.Types.ObjectId; + +schema.set('strict', false); +const OldDashboardModel = getConnection().model('Dashboard', schema, 'dashboards'); + +const up = async () => { + logger.info('Moving existing shared dashboards into new array format.'); + const dashboards = await Dashboard.find({}).lean().exec(); + + const updatePromises = map(dashboards, async (dashboard) => { + if (!dashboard.shareable) { + dashboard.shareable = []; + } + + const shareable = dashboard.shareable; + shareable.unshift({ + title: '~ Shareable', + filter: dashboard.filter, + visibility: dashboard.visibility, + validDomains: dashboard.validDomains, + createdAt: new Date(), + }); + + return OldDashboardModel.update( + { _id: objectId(dashboard._id) }, + { + shareable, + }, + { + safe: false, + strict: false + } + ); + }); + + await Promise.all(updatePromises); +}; + +const down = async () => { + logger.info('Moving first shared link in dashboards back into old format.'); + const dashboards = await OldDashboardModel.find({}).exec(); + + const updatePromises = map(dashboards, (dashboard) => { + if (!dashboard.shareable || dashboard.shareable.length < 1) { + return Promise.resolve(); + } + + const shareable = dashboard.shareable.shift(); + + return OldDashboardModel.update( + { _id: objectId(dashboard._id) }, + { + filter: shareable.filter, + visibility: shareable.visibility, + validDomains: shareable.validDomains, + shareable: dashboard.shareable, + }, + { + safe: false, + strict: false + } + ); + }); + + await Promise.all(updatePromises); +}; + +export default { up, down }; diff --git a/cli/src/commands/v2-migrations/index.js b/cli/src/commands/v2-migrations/index.js index 9c5064f272..a594f05c51 100644 --- a/cli/src/commands/v2-migrations/index.js +++ b/cli/src/commands/v2-migrations/index.js @@ -5,11 +5,13 @@ import updateRefs from './20171121153300_update_refs'; import migrateIdentifiers from './20171127214900_migrate_identifiers'; import personasIndexes from './20171008104700_personas_indexes'; // import removeOldIdents from './20171128144900_remove_old_idents'; +import shareableDashboards from './20180320093000_shareable_dashboards'; export default new OrderedMap() .set('20171122100800_common_indexes', commonIndexesMigration) .set('20171121153300_update_refs', updateRefs) .set('20171008104700_personas_indexes', personasIndexes) - .set('20171127214900_migrate_identifiers', migrateIdentifiers); + .set('20171127214900_migrate_identifiers', migrateIdentifiers) // .set('20171127214500_remove_unused_persona_props', removeUnusedPersonaProps) // .set('20171128144900_remove_old_idents', removeOldIdents) + .set('20180320093000_shareable_dashboards', shareableDashboards); diff --git a/lib/models/dashboard.js b/lib/models/dashboard.js index 253748615b..2f3a60ba31 100644 --- a/lib/models/dashboard.js +++ b/lib/models/dashboard.js @@ -31,10 +31,11 @@ const shareableSchema = new mongoose.Schema({ title: { type: String, default: '~ Shareable' }, filter: { type: String, default: '{}' }, visibility: { type: String, enum: sharingScopes, default: NOWHERE }, - validDomains: { type: String } + validDomains: { type: String }, + createdAt: { type: Date, default: new Date() }, }); -const schema = new mongoose.Schema({ +export const schema = new mongoose.Schema({ title: { type: String }, widgets: [widgetSchema], shareable: [shareableSchema], diff --git a/lib/models/plugins/tests/softDelete-test.js b/lib/models/plugins/tests/softDelete-test.js index 0276d3f5fb..bb3a26a3ef 100644 --- a/lib/models/plugins/tests/softDelete-test.js +++ b/lib/models/plugins/tests/softDelete-test.js @@ -12,7 +12,8 @@ const schemaFactory = (fields, pluginOpts = {}) => { }; describe('softDeletePlugin', () => { - describe('default options', () => { + describe('default options', function describeTest() { + this.timeout(10000); let TestModel; before('setup schema', (done) => { TestModel = schemaFactory({ diff --git a/lib/models/statement.js b/lib/models/statement.js index cb40a614dd..ae4f603654 100644 --- a/lib/models/statement.js +++ b/lib/models/statement.js @@ -90,7 +90,10 @@ schema.plugin(filterByOrg); schema.plugin(addCRUDFunctions); const streamAggregation = ({ pipeline, skip, limit, batchSize, maxTimeMS, maxScan }) => { - let query = Statement.aggregate(pipeline).allowDiskUse(ALLOW_AGGREGATION_DISK_USE); + let query = Statement + .aggregate(pipeline) + .read('secondaryPreferred') + .allowDiskUse(ALLOW_AGGREGATION_DISK_USE); if (skip !== -1) query = query.skip(skip); if (limit !== -1) query = query.limit(limit); if (!query.options) { @@ -154,19 +157,19 @@ const setCachedAggregation = ({ client, dataKey, isRunningKey, stream }) => * @return {Stream} stream */ schema.statics.aggregateByAuth = function aggregateByAuth( - authInfo, - pipeline = [], - { - skip = 0, - limit = -1, - cache = false, - batchSize = 100, - getStream = false, - maxTimeMS = MAX_TIME_MS, - maxScan = MAX_SCAN, - }, - cb = () => {} - ) { + authInfo, + pipeline = [], + { + skip = 0, + limit = -1, + cache = false, + batchSize = 100, + getStream = false, + maxTimeMS = MAX_TIME_MS, + maxScan = MAX_SCAN, + }, + cb = () => { } +) { return parseQuery(pipeline, { organisation: getOrgFromAuthInfo(authInfo) }).then(async (parsedPipeline) => { @@ -228,9 +231,9 @@ schema.statics.aggregateByAuth = function aggregateByAuth( } return dataKeyTTL >= 5 ? cachedStreamPromise : streamPromise; }) - .then((stringResult) => { - cb(null, stringResult); - }); + .then((stringResult) => { + cb(null, stringResult); + }); }).catch(cb); }; diff --git a/lib/routes/index.js b/lib/routes/index.js index 2544f90f93..ab286dc0e9 100644 --- a/lib/routes/index.js +++ b/lib/routes/index.js @@ -61,7 +61,8 @@ const routes = [ { name: 'organisation.settings.roles', path: '/roles', canActivate: requireModelViewScope('role') }, // Embedded dashboards - { name: 'embedded-dashboard', path: '/dashboards/:dashboardId/:shareableId/*splat' } + { name: 'embedded-dashboard', path: '/dashboards/:dashboardId' }, + { name: 'embedded-dashboard.shareable', path: '/:shareableId/*splat' } ]; const router = createRouter( diff --git a/lib/tools/getWebpackConfig.js b/lib/tools/getWebpackConfig.js index 690b11d938..b02ddedfa4 100644 --- a/lib/tools/getWebpackConfig.js +++ b/lib/tools/getWebpackConfig.js @@ -5,6 +5,7 @@ const AssetsPlugin = require('assets-webpack-plugin'); const StatsPlugin = require('stats-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin'); const nodeExternals = require('webpack-node-externals'); +const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); // Common configuration chunk to be used for both // client-side and server-side bundles @@ -148,6 +149,8 @@ function getWebpackConfig(args) { }, plugins: [ + new HardSourceWebpackPlugin(), + new webpack.optimize.ModuleConcatenationPlugin(), // Emit a file with assets paths // https://github.com/sporto/assets-webpack-plugin#options new AssetsPlugin({ diff --git a/package.json b/package.json index b2cbf06f2e..f71f21dbee 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "file-loader": "^0.8.5", "file-stream-rotator": "^0.0.6", "git-rev": "^0.2.1", + "hard-source-webpack-plugin": "^0.5.15", "helmet": "^2.1.1", "highland": "^2.8.1", "hoist-non-react-statics": "^1.0.3", @@ -232,7 +233,7 @@ "velocity-react": "1.1.4", "victory": "^0.12.1", "warning": "^2.1.0", - "webpack": "^2.2.1", + "webpack": "^3.10", "webpack-dev-middleware": "^1.10.1", "webpack-node-externals": "^1.5.4", "winston": "^2.1.1", diff --git a/ui/src/containers/App/index.js b/ui/src/containers/App/index.js index 9d7d734224..d0f4a69f38 100644 --- a/ui/src/containers/App/index.js +++ b/ui/src/containers/App/index.js @@ -44,7 +44,9 @@ const renderPage = (routeName) => { loader: System.import('ui/containers/ResetPassword') })); } - if (testRoute('embedded-dashboard')) { + if ( + testRoute('embedded-dashboard') + ) { return React.createElement(createAsyncComponent({ loader: System.import('ui/containers/EmbeddableDashboard') })); diff --git a/ui/src/containers/DashboardSharing/OpenLinkButton/index.js b/ui/src/containers/DashboardSharing/OpenLinkButton/index.js new file mode 100644 index 0000000000..3d378d8f68 --- /dev/null +++ b/ui/src/containers/DashboardSharing/OpenLinkButton/index.js @@ -0,0 +1,48 @@ +/* eslint-disable react/jsx-indent */ +import React, { Component, PropTypes } from 'react'; +import classNames from 'classnames'; + +class OpenLinkButton extends Component { + static propTypes = { + className: PropTypes.string, + openLink: PropTypes.func, + white: PropTypes.bool, + small: PropTypes.bool, + disabled: PropTypes.bool, + } + + static defaultProps = { + className: '', + disabled: false, + } + + onClick = (e) => { + e.preventDefault(); + e.stopPropagation(); + this.props.openLink(); + } + + render = () => { + const { className } = this.props; + + const classes = classNames({ + [className]: true, + btn: true, + 'btn-sm': !this.props.small, + 'btn-xs': this.props.small, + 'btn-inverse': !this.props.white && !this.props.small, + 'flat-white flat-btn': this.props.white, + 'btn-default': this.props.small || this.props.white + }); + const width = this.props.small ? '22.5px' : '33px'; + + + return ( + + ); + } +} + +export default OpenLinkButton; \ No newline at end of file diff --git a/ui/src/containers/DashboardSharing/index.js b/ui/src/containers/DashboardSharing/index.js index 2be1b3009e..566e10ba66 100644 --- a/ui/src/containers/DashboardSharing/index.js +++ b/ui/src/containers/DashboardSharing/index.js @@ -23,6 +23,7 @@ import { } from 'lib/constants/sharingScopes'; import RadioGroup from 'ui/components/Material/RadioGroup'; import RadioButton from 'ui/components/Material/RadioButton'; +import OpenLinkButtonComponent from './OpenLinkButton'; import styles from './styles.css'; const schema = 'dashboard'; @@ -183,12 +184,23 @@ const deleteButton = ({ parentModel }) => compose( }) )(DeleteButtonComponent); +const openLinkButton = ({ parentModel }) => compose( + withHandlers({ + openLink: ({ id }) => () => { + const model = parentModel.get('shareable').find(mod => mod.get('_id') === id); + const url = getShareableUrl({ model, parentModel }); + window.open(url, `shareable-${parentModel.get('_id')}-${model.get('_id')}`); + } + }) +)(OpenLinkButtonComponent); + // -------------------------- const dashboardSharingHandlers = withHandlers({ addShareable: ({ updateModel, model }) => () => { const newShareable = model.get('shareable', new List()).push(new Map({ - title: '~ Shareable' + title: '~ Shareable', + createdAt: new Date(), })); updateModel({ path: 'shareable', @@ -213,13 +225,18 @@ const DashboardSharingComponent = ({ mod.get('title')} + isLoading={false} + hasMore={false} models={model.get('shareable', new List())} + fetchMore={() => {}} + ModelForm={ModelForm} ModelListItem={ModelListItemWithoutModel} parentModel={model} updateModel={updateModel} - buttons={[(deleteButton({ parentModel: model }))]} /> + buttons={[(openLinkButton({ parentModel: model })), (deleteButton({ parentModel: model }))]} + getDescription={mod => mod.get('title')} + noItemsDisplay="No shared links - click 'Add new link' to share your dashboard" /> +
); diff --git a/ui/src/containers/ModelList/index.js b/ui/src/containers/ModelList/index.js index 790e2da027..bb9d7593af 100644 --- a/ui/src/containers/ModelList/index.js +++ b/ui/src/containers/ModelList/index.js @@ -24,16 +24,20 @@ const enhance = compose( model: PropTypes.object, fetchMore: PropTypes.func.isRequired, ModelForm: PropTypes.func.isRequired, + getModelKey: PropTypes.func, displayOwner: PropTypes.bool, buttons: PropTypes.arrayOf(PropTypes.func), modifyButtons: PropTypes.func, - getDescription: PropTypes.func + getDescription: PropTypes.func, + noItemsDisplay: PropTypes.string, }), defaultProps({ + getModelKey: model => model.get('_id', Math.random().toString()), getDescription: model => model.get('description', ''), buttons: [DeleteButton], displayOwner: true, - modifyButtons: buttons => buttons + modifyButtons: buttons => buttons, + noItemsDisplay: 'No Items', }), connect( (state, { @@ -109,11 +113,13 @@ const render = ({ schema, ModelForm, getDescription, + getModelKey, buttons, hasMore, fetchMore, modifyButtons, ModelListItem: ModelListItemToUse = ModelListItem, + noItemsDisplay, ...other }) => { if (modelsWithModel.size > 0) { @@ -122,7 +128,7 @@ const render = ({ { modelsWithModel.map(model => -

No items.

+

{ noItemsDisplay }

); }; diff --git a/ui/src/containers/UserForm/index.js b/ui/src/containers/UserForm/index.js index 5018be91cd..39442ffff1 100644 --- a/ui/src/containers/UserForm/index.js +++ b/ui/src/containers/UserForm/index.js @@ -9,6 +9,12 @@ import ValidationList from 'ui/components/ValidationList'; import Checkbox from 'ui/components/Material/Checkbox'; import uuid from 'uuid'; import { validatePasswordUtil } from 'lib/utils/validators/User'; +import { connect } from 'react-redux'; +import { + hasScopeSelector, + loggedInUserId as loggedInUserIdSelector +} from 'ui/redux/modules/auth'; +import { SITE_ADMIN } from 'lib/constants/scopes'; import styles from './userform.css'; const changeModelAttr = (updateModel, model, attr) => value => @@ -170,6 +176,8 @@ const render = ({ setPassword, passwordConfirmation, setPasswordConfirmation, + isSiteAdmin, + loggedInUserId }) => { const ownerOrganisationSettings = model.get('ownerOrganisationSettings', new Map()).toJS(); @@ -179,7 +187,12 @@ const render = ({ password, passwordConfirmation, ownerOrganisationSettings ).concat(password === '' ? serverErrors : new List()); const hasPasswordErrors = !passwordErrors.isEmpty(); - const canChangePassword = (changePasswordChecked || hasPasswordErrors); + const canChangePassword = + (changePasswordChecked || hasPasswordErrors); + const isAuthorisedToChangePassword = ( + isSiteAdmin || + model.get('_id') === loggedInUserId + ); const passwordInputsVisible = (!model.get('verified') || canChangePassword); const passwordGroupClasses = classNames({ 'form-group': true, @@ -195,7 +208,7 @@ const render = ({ {renderVerified(model, styles)} {renderName(model, onChangeAttr)} {renderEmail(model, onChangeAttr)} - {renderPasswordChanges(model, onPasswordCheckboxChange(updateModel, model, setChangePasswordChecked), canChangePassword)} + {isAuthorisedToChangePassword && renderPasswordChanges(model, onPasswordCheckboxChange(updateModel, model, setChangePasswordChecked), canChangePassword)} {passwordInputsVisible && (
@@ -229,5 +242,9 @@ export default compose( schema: 'user', id: model.get('_id') })), + connect(state => ({ + isSiteAdmin: hasScopeSelector(SITE_ADMIN)(state), + loggedInUserId: loggedInUserIdSelector(state) + })), withModel )(render); diff --git a/ui/src/controllers/renderDashboard.js b/ui/src/controllers/renderDashboard.js index 2de2507f1c..4a320c38a8 100644 --- a/ui/src/controllers/renderDashboard.js +++ b/ui/src/controllers/renderDashboard.js @@ -37,9 +37,16 @@ export default (req, res) => { Dashboard.findById(dashboardId).then((dashboard) => { if (dashboard === null) throw new Error('Dashboard not found'); - const shareableDashboard = find(dashboard.shareable, share => - share._id.toString() === shareableId - ); + let shareableDashboard; + if (shareableId) { + shareableDashboard = find(dashboard.shareable, share => + share._id.toString() === shareableId + ); + } else if (dashboard.shareable.length > 0) { + shareableDashboard = dashboard.shareable[0]; + } else { + throw new Error('This dashboard has not been shared'); + } const dashboardWithShareable = dashboard; diff --git a/ui/src/server.js b/ui/src/server.js index 506c343c3f..acfeca3648 100644 --- a/ui/src/server.js +++ b/ui/src/server.js @@ -81,6 +81,7 @@ proxy.on('error', (error, req, res) => { }); app.use('/dashboards/:dashboardId/:shareableId', renderDashboard); +app.use('/dashboards/:dashboardId', renderDashboard); app.use('*', renderApp); if (config.port) { diff --git a/yarn.lock b/yarn.lock index ef5af722a1..9143210980 100644 --- a/yarn.lock +++ b/yarn.lock @@ -380,10 +380,14 @@ agent-base@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-1.0.2.tgz#6890d3fb217004b62b70f8928e0fae5f8952a706" -ajv-keywords@^1.0.0, ajv-keywords@^1.1.1: +ajv-keywords@^1.0.0: version "1.5.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" +ajv-keywords@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.1.0.tgz#ac2b27939c543e95d2c06e7f7f5c27be4aa543be" + ajv@^4.7.0, ajv@^4.9.1: version "4.11.8" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" @@ -400,6 +404,14 @@ ajv@^5.1.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" +ajv@^6.1.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.1.1.tgz#978d597fbc2b7d0e5a5c3ddeb149a682f2abfa0e" + dependencies: + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -2388,7 +2400,7 @@ camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" -camelcase@^4.0.0: +camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" @@ -3754,6 +3766,10 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" +detect-indent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + detect-libc@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" @@ -4139,7 +4155,7 @@ engine.io@~3.1.0: optionalDependencies: uws "~9.14.0" -enhanced-resolve@^3.3.0: +enhanced-resolve@^3.4.0: version "3.4.1" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" dependencies: @@ -4959,7 +4975,7 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" -find-up@^2.1.0: +find-up@^2.0.0, find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" dependencies: @@ -5654,6 +5670,20 @@ har-validator@~5.0.3: ajv "^5.1.0" har-schema "^2.0.0" +hard-source-webpack-plugin@^0.5.15: + version "0.5.18" + resolved "https://registry.yarnpkg.com/hard-source-webpack-plugin/-/hard-source-webpack-plugin-0.5.18.tgz#4f328e344ad5305227c7db526d5365e8d7786712" + dependencies: + lodash "^4.15.0" + mkdirp "^0.5.1" + node-object-hash "^1.2.0" + rimraf "^2.6.2" + source-list-map "^0.1.6" + source-map "^0.5.6" + webpack-core "~0.6.0" + webpack-sources "^1.0.1" + write-json-file "^2.3.0" + harmony-reflect@^1.4.6: version "1.5.1" resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.5.1.tgz#b54ca617b00cc8aef559bbb17b3d85431dc7e329" @@ -7556,6 +7586,15 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + loader-fs-cache@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.1.tgz#56e0bf08bd9708b26a765b68509840c8dec9fdbc" @@ -8129,6 +8168,12 @@ media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" +mem@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" + dependencies: + mimic-fn "^1.0.0" + memory-fs@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290" @@ -8292,6 +8337,10 @@ mimeparse@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/mimeparse/-/mimeparse-0.1.4.tgz#dafb02752370fd226093ae3152c271af01ac254a" +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + min-document@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -8868,6 +8917,10 @@ node-notifier@^5.0.1, node-notifier@^5.1.2: shellwords "^0.1.1" which "^1.3.0" +node-object-hash@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/node-object-hash/-/node-object-hash-1.3.0.tgz#7f294f5afec6b08d713e40d40a95ec793e05baf3" + node-pre-gyp@^0.6.39: version "0.6.39" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" @@ -9289,6 +9342,14 @@ os-locale@^1.4.0: dependencies: lcid "^1.0.0" +os-locale@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" + dependencies: + execa "^0.7.0" + lcid "^1.0.0" + mem "^1.1.0" + os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -9602,6 +9663,12 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -11113,6 +11180,13 @@ read-pkg-up@^1.0.1: find-up "^1.0.0" read-pkg "^1.0.0" +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + read-pkg@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" @@ -11121,6 +11195,14 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + "readable-stream@1 || 2", readable-stream@2, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3: version "2.3.4" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.4.tgz#c946c3f47fa7d8eabc0b6150f4a12f69a4574071" @@ -12339,7 +12421,13 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -source-list-map@^0.1.4, source-list-map@~0.1.7: +sort-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" + dependencies: + is-plain-obj "^1.0.0" + +source-list-map@^0.1.4, source-list-map@^0.1.6, source-list-map@~0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" @@ -12775,7 +12863,7 @@ supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3: dependencies: has-flag "^1.0.0" -supports-color@^4.0.0: +supports-color@^4.0.0, supports-color@^4.2.1: version "4.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" dependencies: @@ -12832,7 +12920,7 @@ tapable@^0.1.8, tapable@~0.1.8: version "0.1.10" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4" -tapable@^0.2.7, tapable@~0.2.5: +tapable@^0.2.7: version "0.2.8" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" @@ -13151,7 +13239,7 @@ ua-parser-js@^0.7.9: version "0.7.17" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" -uglify-js@^2.6, uglify-js@^2.8.27: +uglify-js@^2.6, uglify-js@^2.8.29: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" dependencies: @@ -13181,6 +13269,14 @@ uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" +uglifyjs-webpack-plugin@^0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309" + dependencies: + source-map "^0.5.6" + uglify-js "^2.8.29" + webpack-sources "^1.0.1" + uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" @@ -13606,7 +13702,7 @@ watchpack@^0.2.1: chokidar "^1.0.0" graceful-fs "^4.1.2" -watchpack@^1.3.1: +watchpack@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" dependencies: @@ -13632,7 +13728,7 @@ webidl-conversions@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" -webpack-core@~0.6.9: +webpack-core@~0.6.0, webpack-core@~0.6.9: version "0.6.9" resolved "https://registry.yarnpkg.com/webpack-core/-/webpack-core-0.6.9.tgz#fc571588c8558da77be9efb6debdc5a3b172bdc2" dependencies: @@ -13710,31 +13806,32 @@ webpack@^1.12.9: watchpack "^0.2.1" webpack-core "~0.6.9" -webpack@^2.2.1: - version "2.7.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.7.0.tgz#b2a1226804373ffd3d03ea9c6bd525067034f6b1" +webpack@^3.10: + version "3.11.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.11.0.tgz#77da451b1d7b4b117adaf41a1a93b5742f24d894" dependencies: acorn "^5.0.0" acorn-dynamic-import "^2.0.0" - ajv "^4.7.0" - ajv-keywords "^1.1.1" + ajv "^6.1.0" + ajv-keywords "^3.1.0" async "^2.1.2" - enhanced-resolve "^3.3.0" + enhanced-resolve "^3.4.0" + escope "^3.6.0" interpret "^1.0.0" json-loader "^0.5.4" json5 "^0.5.1" loader-runner "^2.3.0" - loader-utils "^0.2.16" + loader-utils "^1.1.0" memory-fs "~0.4.1" mkdirp "~0.5.0" node-libs-browser "^2.0.0" source-map "^0.5.3" - supports-color "^3.1.0" - tapable "~0.2.5" - uglify-js "^2.8.27" - watchpack "^1.3.1" + supports-color "^4.2.1" + tapable "^0.2.7" + uglifyjs-webpack-plugin "^0.4.6" + watchpack "^1.4.0" webpack-sources "^1.0.1" - yargs "^6.0.0" + yargs "^8.0.2" weedout@~0.1.0: version "0.1.3" @@ -13773,6 +13870,10 @@ which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + which@1, which@^1.1.1, which@^1.2.14, which@^1.2.9, which@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" @@ -13879,6 +13980,17 @@ write-file-stdout@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/write-file-stdout/-/write-file-stdout-0.0.2.tgz#c252d7c7c5b1b402897630e3453c7bfe690d9ca1" +write-json-file@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.3.0.tgz#2b64c8a33004d54b8698c76d585a77ceb61da32f" + dependencies: + detect-indent "^5.0.0" + graceful-fs "^4.1.2" + make-dir "^1.0.0" + pify "^3.0.0" + sort-keys "^2.0.0" + write-file-atomic "^2.0.0" + write@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" @@ -13994,6 +14106,12 @@ yargs-parser@^5.0.0: dependencies: camelcase "^3.0.0" +yargs-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" + dependencies: + camelcase "^4.1.0" + yargs@^3.10.0: version "3.32.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" @@ -14025,7 +14143,7 @@ yargs@^4.8.0: y18n "^3.2.1" yargs-parser "^2.4.1" -yargs@^6.0.0, yargs@^6.3.0: +yargs@^6.3.0: version "6.6.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" dependencies: @@ -14061,6 +14179,24 @@ yargs@^7.0.0: y18n "^3.2.1" yargs-parser "^5.0.0" +yargs@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" + dependencies: + camelcase "^4.1.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + read-pkg-up "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^7.0.0" + yargs@~3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"