diff --git a/.eslintrc b/.eslintrc index 6267cb94cda4ac..c76b15de8dd464 100644 --- a/.eslintrc +++ b/.eslintrc @@ -14,6 +14,7 @@ "require": false, "expect": false, "sinon": false, + "MockApiClient": true, "TestStubs": true, "Raven": true, "jest": true diff --git a/package.json b/package.json index 70d80976e425fb..c718d5b1494f74 100644 --- a/package.json +++ b/package.json @@ -104,5 +104,8 @@ "sinon": "1.17.2", "sinon-chai": "2.8.0", "webpack-livereload-plugin": "^0.11.0" + }, + "optionalDependencies": { + "fsevents": "^1.1.2" } } diff --git a/src/sentry/static/sentry/app/__mocks__/api.jsx b/src/sentry/static/sentry/app/__mocks__/api.jsx new file mode 100644 index 00000000000000..b5ca2e34368c02 --- /dev/null +++ b/src/sentry/static/sentry/app/__mocks__/api.jsx @@ -0,0 +1,51 @@ +export class Request {} + +export class Client { + static mockResponses = []; + + static clearMockResponses() { + Client.mockResponses = []; + } + + static addMockResponse(response) { + Client.mockResponses.push({ + statusCode: 200, + body: '', + method: 'GET', + ...response + }); + } + + static findMockResponse(url, options) { + return Client.mockResponses.find(response => { + return url === response.url && (options.method || 'GET') === response.method; + }); + } + + request(url, options) { + let response = Client.findMockResponse(url, options); + if (!response) { + console.error( + 'No mocked response found for request.', + url, + options.method || 'GET' + ); + options.error && + options.error({ + status: 404, + responseText: 'HTTP 404', + responseJSON: null + }); + } else if (response.statusCode !== 200) { + options.error && + options.error({ + status: response.statusCode, + responseText: JSON.stringify(response.body), + responseJSON: response.body + }); + } else { + options.success && options.success(response.body); + } + options.complete && options.complete(); + } +} diff --git a/src/sentry/static/sentry/app/components/forms/rangeField.jsx b/src/sentry/static/sentry/app/components/forms/rangeField.jsx index 2a4a7f2c160d60..fbf08a3b2c7a69 100644 --- a/src/sentry/static/sentry/app/components/forms/rangeField.jsx +++ b/src/sentry/static/sentry/app/components/forms/rangeField.jsx @@ -21,7 +21,6 @@ export default class RangeField extends InputField { static defaultProps = { ...InputField.defaultProps, - onChange: value => {}, formatLabel: value => value, min: 0, max: 100, @@ -55,7 +54,7 @@ export default class RangeField extends InputField { .on('slider:changed', (e, data) => { let value = parseInt(data.value, 10); $value.html(this.props.formatLabel(value)); - this.props.onChange(value); + this.setValue(value); }) .simpleSlider({ value: this.props.defaultValue || this.props.value, diff --git a/src/sentry/static/sentry/app/views/asyncView.jsx b/src/sentry/static/sentry/app/views/asyncView.jsx index 3ee54708a3fbac..cb3691d18906d8 100644 --- a/src/sentry/static/sentry/app/views/asyncView.jsx +++ b/src/sentry/static/sentry/app/views/asyncView.jsx @@ -33,11 +33,18 @@ class AsyncView extends React.Component { // XXX: cant call this getInitialState as React whines getDefaultState() { - return { - data: null, + let endpoints = this.getEndpoints(); + let state = { + // has all data finished requesting? loading: true, - error: false + // is there an error loading ANY data? + error: false, + errors: {} }; + endpoints.forEach(([stateKey, endpoint]) => { + state[stateKey] = null; + }); + return state; } remountComponent() { @@ -46,41 +53,74 @@ class AsyncView extends React.Component { // TODO(dcramer): we'd like to support multiple initial api requests fetchData() { - let endpoint = this.getEndpoint(); - if (!endpoint) { + let endpoints = this.getEndpoints(); + if (!endpoints.length) { this.setState({ loading: false, error: false }); - } else { + return; + } + // TODO(dcramer): this should cancel any existing API requests + this.setState({ + loading: true, + error: false, + remainingRequests: endpoints.length + }); + endpoints.forEach(([stateKey, endpoint, params]) => { this.api.request(endpoint, { method: 'GET', - params: this.getEndpointParams(), + params: params, success: (data, _, jqXHR) => { - this.setState({ - loading: false, - error: false, - data: data + this.setState(prevState => { + return { + [stateKey]: data, + remainingRequests: prevState.remainingRequests - 1, + loading: prevState.remainingRequests > 1 + }; }); }, error: error => { - this.setState({ - loading: false, - error: error + this.setState(prevState => { + return { + [stateKey]: null, + errors: { + ...prevState.errors, + [stateKey]: error + }, + remainingRequests: prevState.remainingRequests - 1, + loading: prevState.remainingRequests > 1, + error: true + }; }); } }); - } + }); } + // DEPRECATED: use getEndpoints() getEndpointParams() { return {}; } + // DEPRECATED: use getEndpoints() getEndpoint() { return null; } + /** + * Return a list of endpoint queries to make. + * + * return [ + * ['stateKeyName', '/endpoint/', {optional: 'query params'}] + * ] + */ + getEndpoints() { + let endpoint = this.getEndpoint(); + if (!endpoint) return []; + return [['data', endpoint, this.getEndpointParams()]]; + } + getTitle() { return 'Sentry'; } @@ -98,7 +138,9 @@ class AsyncView extends React.Component { {this.state.loading ? this.renderLoading() - : this.state.error ? this.renderError(this.state.error) : this.renderBody()} + : this.state.error + ? this.renderError(new Error('Unable to load all required endpoints')) + : this.renderBody()} ); } diff --git a/src/sentry/static/sentry/app/views/organizationCreate.jsx b/src/sentry/static/sentry/app/views/organizationCreate.jsx index 3569f2da7d01ee..806c30370d5cb9 100644 --- a/src/sentry/static/sentry/app/views/organizationCreate.jsx +++ b/src/sentry/static/sentry/app/views/organizationCreate.jsx @@ -32,7 +32,8 @@ export default class OrganizationCreate extends AsyncView { submitLabel={t('Create Organization')} apiEndpoint="/organizations/" apiMethod="POST" - onSubmitSuccess={this.onSubmitSuccess}> + onSubmitSuccess={this.onSubmitSuccess} + requireChanges={true}> { - let loadingIndicator = IndicatorStore.add(t('Saving changes..')); - let {orgId, projectId} = this.props; - this.api.request(`/projects/${orgId}/${projectId}/`, { - method: 'PUT', - data: this.state.formData, - success: data => { - this.props.onSave(data); - this.setState({ - state: FormState.READY, - errors: {} - }); - }, - error: error => { - this.setState({ - state: FormState.ERROR, - errors: error.responseJSON - }); - }, - complete: () => { - IndicatorStore.remove(loadingIndicator); - } - }); - } - ); - }, + }; render() { - let isSaving = this.state.state === FormState.SAVING; - let {errors, formData} = this.state; - let hasChanges = !underscore.isEqual(this.props.initialData, formData); + let {orgId, projectId, initialData, onSave} = this.props; return (
@@ -90,13 +31,12 @@ const DigestSettings = React.createClass({ 'the sliders below.' )}

-
- {this.state.state === FormState.ERROR && -
- {t( - 'Unable to save your changes. Please ensure all fields are valid and try again.' - )} -
} +
@@ -122,96 +59,27 @@ const DigestSettings = React.createClass({ label={t('Maximum delivery interval')} help={t('Notifications will be delivered at least this often.')} name="digestsMaxDelay" - value={formData.digestsMaxDelay} - error={errors.digestsMaxDelay} formatLabel={RangeField.formatMinutes} - onChange={this.onFieldChange.bind(this, 'digestsMaxDelay')} />
- -
- -
- +
); } -}); +} -const GeneralSettings = React.createClass({ - propTypes: { +class GeneralSettings extends React.Component { + static propTypes = { orgId: React.PropTypes.string.isRequired, projectId: React.PropTypes.string.isRequired, initialData: React.PropTypes.object, onSave: React.PropTypes.func.isRequired - }, - - mixins: [ApiMixin], - - getInitialState() { - return { - formData: Object.assign({}, this.props.initialData), - errors: {} - }; - }, - - onFieldChange(name, value) { - this.setState({ - formData: { - ...this.state.formData, - [name]: value - } - }); - }, - - onSubmit(e) { - e.preventDefault(); - - if (this.state.state == FormState.SAVING) { - return; - } - this.setState( - { - state: FormState.SAVING - }, - () => { - let loadingIndicator = IndicatorStore.add(t('Saving changes..')); - let {orgId, projectId} = this.props; - this.api.request(`/projects/${orgId}/${projectId}/`, { - method: 'PUT', - data: this.state.formData, - success: data => { - this.props.onSave(data); - this.setState({ - state: FormState.READY, - errors: {} - }); - }, - error: error => { - this.setState({ - state: FormState.ERROR, - errors: error.responseJSON - }); - }, - complete: () => { - IndicatorStore.remove(loadingIndicator); - } - }); - } - ); - }, + }; render() { - let isSaving = this.state.state === FormState.SAVING; - let {errors, formData} = this.state; - let hasChanges = !underscore.isEqual(this.props.initialData, formData); + let {orgId, projectId, initialData, onSave} = this.props; return (
@@ -219,103 +87,66 @@ const GeneralSettings = React.createClass({
-
- {this.state.state === FormState.ERROR && -
- {t( - 'Unable to save your changes. Please ensure all fields are valid and try again.' - )} -
} - + - -
- -
- +
); } -}); +} -const ProjectAlertSettings = React.createClass({ - propTypes: { +export default class ProjectAlertSettings extends AsyncView { + static propTypes = { + ...AsyncView.propTypes, // these are not declared as required of issues with cloned elements // not initially defining them (though they are bound before) ever // rendered organization: React.PropTypes.object, project: React.PropTypes.object - }, - - mixins: [ApiMixin], - - getInitialState() { - return { - loading: true, - error: false, - pluginList: [] - }; - }, + }; - componentDidMount() { - this.fetchData(); - }, - - fetchData() { + getEndpoints() { let {orgId, projectId} = this.props.params; - this.api.request(`/projects/${orgId}/${projectId}/plugins/`, { - success: (data, _, jqXHR) => { - this.setState({ - error: false, - loading: false, - pluginList: data.filter(p => p.type === 'notification') - }); - }, - error: () => { - this.setState({ - error: true, - loading: false - }); - } - }); - }, + return [ + ['project', `/projects/${orgId}/${projectId}/`], + ['pluginList', `/projects/${orgId}/${projectId}/plugins/`] + ]; + } - onDigestsChange(data) { + onDigestsChange = data => { // TODO(dcramer): propagate this in a more correct way this.setState({ project: { ...this.state.project, - digestsMinDelay: data.digestsMinDelay, - digestsMaxDelay: data.digestsMaxDelay + ...data } }); - }, + }; - onGeneralChange(data) { + onGeneralChange = data => { // TODO(dcramer): propagate this in a more correct way this.setState({ project: { ...this.state.project, - subjectTemplate: data.subjectTemplate + ...data } }); - }, + }; - onEnablePlugin(plugin) { + onEnablePlugin = plugin => { this.setState({ pluginList: this.state.pluginList.map(p => { if (p.id !== plugin.id) return p; @@ -325,9 +156,9 @@ const ProjectAlertSettings = React.createClass({ }; }) }); - }, + }; - onDisablePlugin(plugin) { + onDisablePlugin = plugin => { this.setState({ pluginList: this.state.pluginList.map(p => { if (p.id !== plugin.id) return p; @@ -337,12 +168,15 @@ const ProjectAlertSettings = React.createClass({ }; }) }); - }, + }; - render() { + getTitle() { + return 'Project Alert Settings'; + } + + renderBody() { let {orgId, projectId} = this.props.params; - let {organization, project} = this.props; - let {pluginList} = this.state; + let {organization} = this.props; return (
@@ -386,22 +220,20 @@ const ProjectAlertSettings = React.createClass({ orgId={orgId} projectId={projectId} initialData={{ - digestsMinDelay: project.digestsMinDelay, - digestsMaxDelay: project.digestsMaxDelay + digestsMinDelay: this.state.project.digestsMinDelay, + digestsMaxDelay: this.state.project.digestsMaxDelay }} onSave={this.onDigestsChange} /> p.type === 'notification')} onEnablePlugin={this.onEnablePlugin} onDisablePlugin={this.onDisablePlugin} />
); } -}); - -export default ProjectAlertSettings; +} diff --git a/src/sentry/static/sentry/app/views/teamCreate.jsx b/src/sentry/static/sentry/app/views/teamCreate.jsx index 63d4127cc65f9f..4036c55e7293af 100644 --- a/src/sentry/static/sentry/app/views/teamCreate.jsx +++ b/src/sentry/static/sentry/app/views/teamCreate.jsx @@ -32,7 +32,8 @@ export default class TeamCreate extends AsyncView { submitLabel={t('Save Changes')} apiEndpoint={`/organizations/${orgId}/teams/`} apiMethod="POST" - onSubmitSuccess={this.onSubmitSuccess}> + onSubmitSuccess={this.onSubmitSuccess} + requireChanges={true}> + onSubmitSuccess={this.props.onTeamChange} + requireChanges={true}> { + return { + slug: 'project-slug', + name: 'Project Name', + subjectTemplate: '[$project] ${tag:level}: $title', + digestsMinDelay: 5, + digestsMaxDelay: 60, + ...params + }; + }, + Organization: (...params) => { + return { + slug: 'org-slug', + name: 'Organization Name', + ...params + }; } }; + +// this is very commonly used, so expose it globally +window.MockApiClient = require.requireMock('app/api').Client; + +// default configuration +import ConfigStore from 'app/stores/configStore'; +ConfigStore.loadInitialData({ + user: { + isAuthenticated: true, + email: 'foo@example.com', + options: { + timezone: 'UTC' + } + } +}); diff --git a/tests/js/spec/api.spec.jsx b/tests/js/spec/api.spec.jsx index 96535774fa3b02..bb0d3b5862157b 100644 --- a/tests/js/spec/api.spec.jsx +++ b/tests/js/spec/api.spec.jsx @@ -1,3 +1,5 @@ +jest.unmock('app/api'); + import $ from 'jquery'; import {Client, Request, paramsToQueryArgs} from 'app/api'; import GroupActions from 'app/actions/groupActions'; diff --git a/tests/js/spec/views/__snapshots__/adminSettings.spec.jsx.snap b/tests/js/spec/views/__snapshots__/adminSettings.spec.jsx.snap index 2aa9d71ffc8df6..7300408cc94478 100644 --- a/tests/js/spec/views/__snapshots__/adminSettings.spec.jsx.snap +++ b/tests/js/spec/views/__snapshots__/adminSettings.spec.jsx.snap @@ -4,6 +4,125 @@ exports[`AdminSettings render() renders 1`] = ` - +
+

+ Settings +

+ +

+ General +

+ + + + + +

+ Security & Abuse +

+ + + + +
+
`; diff --git a/tests/js/spec/views/__snapshots__/organizationCreate.spec.jsx.snap b/tests/js/spec/views/__snapshots__/organizationCreate.spec.jsx.snap index 63f772eb7f39a5..372cb42c43c6ea 100644 --- a/tests/js/spec/views/__snapshots__/organizationCreate.spec.jsx.snap +++ b/tests/js/spec/views/__snapshots__/organizationCreate.spec.jsx.snap @@ -23,7 +23,7 @@ exports[`OrganizationCreate render() renders correctly 1`] = ` } } onSubmitSuccess={[Function]} - requireChanges={false} + requireChanges={true} submitDisabled={false} submitLabel="Create Organization" > diff --git a/tests/js/spec/views/__snapshots__/projectAlertSettings.spec.jsx.snap b/tests/js/spec/views/__snapshots__/projectAlertSettings.spec.jsx.snap new file mode 100644 index 00000000000000..b3aac768494c8c --- /dev/null +++ b/tests/js/spec/views/__snapshots__/projectAlertSettings.spec.jsx.snap @@ -0,0 +1,105 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ProjectAlertSettings render() renders 1`] = ` + +
+ + + New Alert Rule + +

+ Alerts +

+ +
+ + + These settings cover rule-based alerts. If you're looking to change which notifications you receive you may do so from your + + + + account settings + + + + . + + +
+ + + +
+ +`; diff --git a/tests/js/spec/views/__snapshots__/teamCreate.spec.jsx.snap b/tests/js/spec/views/__snapshots__/teamCreate.spec.jsx.snap index 489ae564e1cde2..1411804c6d1ec2 100644 --- a/tests/js/spec/views/__snapshots__/teamCreate.spec.jsx.snap +++ b/tests/js/spec/views/__snapshots__/teamCreate.spec.jsx.snap @@ -18,7 +18,7 @@ exports[`TeamCreate render() renders correctly 1`] = ` className="form-stacked" footerClass="form-actions align-right" onSubmitSuccess={[Function]} - requireChanges={false} + requireChanges={true} submitDisabled={false} submitLabel="Save Changes" > diff --git a/tests/js/spec/views/__snapshots__/teamSettings.spec.jsx.snap b/tests/js/spec/views/__snapshots__/teamSettings.spec.jsx.snap index 20b764992789f7..9c49eec5149573 100644 --- a/tests/js/spec/views/__snapshots__/teamSettings.spec.jsx.snap +++ b/tests/js/spec/views/__snapshots__/teamSettings.spec.jsx.snap @@ -23,7 +23,7 @@ exports[`TeamSettings render() renders 1`] = ` } } onSubmitSuccess={[Function]} - requireChanges={false} + requireChanges={true} submitDisabled={false} submitLabel="Save Changes" > diff --git a/tests/js/spec/views/adminSettings.spec.jsx b/tests/js/spec/views/adminSettings.spec.jsx index 5db145f4da4339..bc59f4f8c6f908 100644 --- a/tests/js/spec/views/adminSettings.spec.jsx +++ b/tests/js/spec/views/adminSettings.spec.jsx @@ -8,6 +8,113 @@ import AdminSettings from 'app/views/adminSettings'; // mock the API Response/wait on it describe('AdminSettings', function() { describe('render()', function() { + beforeEach(() => { + MockApiClient.addMockResponse({ + url: '/internal/options/', + body: { + 'system.url-prefix': { + field: { + disabledReason: 'diskPriority', + default: '', + required: true, + disabled: true, + allowEmpty: true, + isSet: true + }, + value: 'https://sentry.example.com' + }, + 'system.admin-email': { + field: { + disabledReason: 'diskPriority', + default: null, + required: true, + disabled: true, + allowEmpty: false, + isSet: true + }, + value: 'foo@example.com' + }, + 'system.support-email': { + field: { + disabledReason: 'diskPriority', + default: null, + required: true, + disabled: true, + allowEmpty: false, + isSet: true + }, + value: 'foo@example.com' + }, + 'system.security-email': { + field: { + disabledReason: 'diskPriority', + default: null, + required: true, + disabled: true, + allowEmpty: false, + isSet: true + }, + value: 'foo@example.com' + }, + 'system.rate-limit': { + field: { + disabledReason: 'diskPriority', + default: 0, + required: true, + disabled: true, + allowEmpty: false, + isSet: true + }, + value: 25 + }, + 'auth.allow-registration': { + field: { + disabledReason: 'diskPriority', + default: false, + required: true, + disabled: true, + allowEmpty: false, + isSet: true + }, + value: true + }, + 'auth.ip-rate-limit': { + field: { + disabledReason: 'diskPriority', + default: 0, + required: true, + disabled: true, + allowEmpty: false, + isSet: true + }, + value: 25 + }, + 'auth.user-rate-limit': { + field: { + disabledReason: 'diskPriority', + default: 0, + required: true, + disabled: true, + allowEmpty: false, + isSet: true + }, + value: 25 + }, + 'api.rate-limit.org-create': { + field: { + disabledReason: 'diskPriority', + default: 0, + required: true, + disabled: true, + allowEmpty: false, + isSet: true + }, + value: 25 + } + } + }); + }); + it('renders', function() { let wrapper = shallow(, { context: { diff --git a/tests/js/spec/views/projectAlertSettings.spec.jsx b/tests/js/spec/views/projectAlertSettings.spec.jsx new file mode 100644 index 00000000000000..9953c2a463fb65 --- /dev/null +++ b/tests/js/spec/views/projectAlertSettings.spec.jsx @@ -0,0 +1,40 @@ +import React from 'react'; +import {shallow} from 'enzyme'; +import toJson from 'enzyme-to-json'; + +import ProjectAlertSettings from 'app/views/projectAlertSettings'; + +describe('ProjectAlertSettings', function() { + beforeEach(function() { + this.org = TestStubs.Organization(); + this.project = TestStubs.Project(); + + MockApiClient.addMockResponse({ + url: `/projects/${this.org.slug}/${this.project.slug}/`, + method: 'GET', + body: this.project + }); + MockApiClient.addMockResponse({ + url: `/projects/${this.org.slug}/${this.project.slug}/plugins/`, + method: 'GET', + body: [] + }); + }); + + describe('render()', function() { + it('renders', function() { + let wrapper = shallow( + , + { + context: { + router: TestStubs.router() + } + } + ); + expect(toJson(wrapper)).toMatchSnapshot(); + }); + }); +}); diff --git a/tests/js/spec/views/stream.spec.jsx b/tests/js/spec/views/stream.spec.jsx index 0b556474547dab..60ed56fdd9bba0 100644 --- a/tests/js/spec/views/stream.spec.jsx +++ b/tests/js/spec/views/stream.spec.jsx @@ -1,3 +1,4 @@ +jest.unmock('app/api'); jest.mock('app/stores/groupStore'); import React from 'react'; diff --git a/yarn.lock b/yarn.lock index 9d586f3336bdc5..08eabf94c0b594 100644 --- a/yarn.lock +++ b/yarn.lock @@ -58,7 +58,7 @@ ajv-keywords@^1.0.0, ajv-keywords@^1.1.1: version "1.5.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.0.tgz#c11e6859eafff83e0dafc416929472eca946aa2c" -ajv@^4.7.0: +ajv@^4.7.0, ajv@^4.9.1: version "4.10.4" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.10.4.tgz#c0974dd00b3464984892d6010aa9c2c945933254" dependencies: @@ -1207,6 +1207,10 @@ caseless@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + center-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" @@ -2345,7 +2349,14 @@ fsevents@^1.0.0: nan "^2.3.0" node-pre-gyp "^0.6.29" -fstream-ignore@~1.0.5: +fsevents@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4" + dependencies: + nan "^2.3.0" + node-pre-gyp "^0.6.36" + +fstream-ignore@^1.0.5, fstream-ignore@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" dependencies: @@ -2353,7 +2364,7 @@ fstream-ignore@~1.0.5: inherits "2" minimatch "^3.0.0" -fstream@^1.0.0, fstream@^1.0.2, fstream@~1.0.10: +fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2, fstream@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.10.tgz#604e8a92fe26ffd9f6fae30399d4984e1ab22822" dependencies: @@ -2380,6 +2391,19 @@ gauge@~2.6.0: strip-ansi "^3.0.1" wide-align "^1.1.0" +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + generate-function@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" @@ -2477,6 +2501,10 @@ handlebars@^4.0.3: optionalDependencies: uglify-js "^2.6" +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + har-validator@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" @@ -2486,6 +2514,13 @@ har-validator@~2.0.6: is-my-json-valid "^2.12.4" pinkie-promise "^2.0.0" +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -3765,6 +3800,20 @@ node-pre-gyp@^0.6.29, node-pre-gyp@^0.6.4: tar "~2.2.0" tar-pack "~3.1.0" +node-pre-gyp@^0.6.36: + version "0.6.36" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786" + dependencies: + mkdirp "^0.5.1" + nopt "^4.0.1" + npmlog "^4.0.2" + rc "^1.1.7" + request "^2.81.0" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^2.2.1" + tar-pack "^3.4.0" + node-uuid@~1.4.7: version "1.4.7" resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.7.tgz#6da5a17668c4b3dd59623bda11cf7fa4c1f60a6f" @@ -3778,6 +3827,13 @@ node-zopfli@^2.0.0: nan "^2.0.0" node-pre-gyp "^0.6.4" +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + dependencies: + abbrev "1" + osenv "^0.1.4" + nopt@~3.0.1: version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" @@ -3819,6 +3875,15 @@ npmlog@4.x: gauge "~2.6.0" set-blocking "~2.0.0" +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + nth-check@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" @@ -3900,7 +3965,7 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" -once@^1.3.0, once@^1.4.0: +once@^1.3.0, once@^1.3.3, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: @@ -3952,10 +4017,17 @@ os-locale@^1.4.0: dependencies: lcid "^1.0.0" -os-tmpdir@^1.0.1: +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" +osenv@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + p-limit@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" @@ -4051,6 +4123,10 @@ pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + phantomjs-prebuilt@2.1.14: version "2.1.14" resolved "https://registry.yarnpkg.com/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.14.tgz#d53d311fcfb7d1d08ddb24014558f1188c516da0" @@ -4458,6 +4534,10 @@ qs@~6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442" +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + query-string@2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/query-string/-/query-string-2.4.2.tgz#7db0666420804baa92ae9f268962855a76143dfb" @@ -4512,6 +4592,15 @@ raw-body@~2.1.5: iconv-lite "0.4.13" unpipe "1.0.0" +rc@^1.1.7: + version "1.2.1" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95" + dependencies: + deep-extend "~0.4.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + rc@~1.1.0: version "1.1.6" resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.6.tgz#43651b76b6ae53b5c802f1151fa3fc3b059969c9" @@ -4672,7 +4761,7 @@ readable-stream@1.1, readable-stream@^1.0.27-1, readable-stream@^1.1.13: isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.1.0, readable-stream@^2.2.2: +"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.1.0, readable-stream@^2.1.4, readable-stream@^2.2.2: version "2.2.9" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.9.tgz#cf78ec6f4a6d1eb43d26488cac97f042e74b7fc8" dependencies: @@ -4877,6 +4966,33 @@ request@^2.51.0: tough-cookie "~2.3.0" tunnel-agent "~0.4.1" +request@^2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -4925,6 +5041,12 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.4.4, rimraf@~2.5.0, rimraf@~2.5.1: dependencies: glob "^7.0.5" +rimraf@^2.5.1, rimraf@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" + dependencies: + glob "^7.0.5" + ripemd160@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-0.2.0.tgz#2bf198bde167cacfa51c0a928e84b68bbe171fce" @@ -4943,6 +5065,10 @@ rx-lite@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" +safe-buffer@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + samsam@1.1.2, samsam@~1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567" @@ -5259,6 +5385,19 @@ tapable@^0.2.5, tapable@~0.2.5: version "0.2.6" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d" +tar-pack@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984" + dependencies: + debug "^2.2.0" + fstream "^1.0.10" + fstream-ignore "^1.0.5" + once "^1.3.3" + readable-stream "^2.1.4" + rimraf "^2.5.1" + tar "^2.2.1" + uid-number "^0.0.6" + tar-pack@~3.1.0: version "3.1.4" resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.1.4.tgz#bc8cf9a22f5832739f12f3910dac1eb97b49708c" @@ -5272,7 +5411,7 @@ tar-pack@~3.1.0: tar "~2.2.1" uid-number "~0.0.6" -tar@~2.2.0, tar@~2.2.1: +tar@^2.2.1, tar@~2.2.0, tar@~2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" dependencies: @@ -5363,6 +5502,12 @@ tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + tunnel-agent@~0.4.1: version "0.4.3" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" @@ -5417,7 +5562,7 @@ 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" -uid-number@~0.0.6: +uid-number@^0.0.6, uid-number@~0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"