Merge pull request #22 from hashicorp/chore-scope-service

Scope Service - icu-136
This commit is contained in:
Randall Morey authored and GitHub committed 2020-05-18 12:45:58 -04:00
commit 876dc653dc
16 files changed
+802 -14

No files matched your search

+2 -1
View File
@@ -21,7 +21,8 @@ module.exports = {
browser: true
},
rules: {
'ember/no-jquery': 'error'
'ember/no-jquery': 'error',
'ember/no-new-mixins': 0
},
overrides: [
// node files
+53
View File
@@ -0,0 +1,53 @@
import Mixin from '@ember/object/mixin';
import { inject as service } from '@ember/service';
/**
* Add this mixin to resource adapters which should be accessed via an
* org/project-scoped URL. This mixin appends a scope path based on the scope
* reported by `ScopeService`.
*/
export default Mixin.create({
// =services
scope: service(),
// =properties
/**
* If true and the current scope has a project, will include the project in
* the resource path. Set to false for resources that are scoped exclusively
* to org.
* @type {boolean}
*/
includeProject: true,
// =methods
/**
* Appends a scope path to the URL prefix, if a scope is set.
* @override
* @return {string}
*/
urlPrefix() {
const prefix = this._super(...arguments);
let scopePath = this.scopePath;
// Ensure a slash is added between prefix + scope path if needed.
if (scopePath && prefix.charAt(prefix.length - 1) !== '/') {
scopePath = `/${scopePath}`;
}
return `${prefix}${scopePath}`;
},
/**
* Generates a scope path for the current scope as reported by `ScopeService`.
* If no scope is set, returns empty string.
* @return {string}
*/
get scopePath() {
const { org, project } = this.scope;
const scopePath = [];
if (org) scopePath.push('orgs', org.id);
if (project && this.includeProject) scopePath.push('projects', project.id);
return scopePath.join('/');
},
});
+123
View File
@@ -0,0 +1,123 @@
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { computed } from '@ember/object';
/**
* This module includes two parts:
*
* - `Scope` class: a data structure representing an org/project tuple
* - `ScopeService`: manages the scope and exposes it to the application
*/
/**
* Represents a _scope_, which is defined as an organization/project tuple.
* This class is intended as a simple data structure and is used by the
* scope service (see below).
* Immutable.
*/
class Scope {
/**
* @type {?object}
* @private
*/
#org = null;
/**
* @type {?object}
* @private
*/
#project = null;
/**
* Initializes an instance of `Scope` with optional org and project.
* A scope with only project is invalid and in thi scenario, project will
* be null.
* @param {?object} org
* @param {?object} project
*/
constructor(org = null, project = null) {
if (org) this.#org = org;
if (org && project) this.#project = project;
}
/**
* Org for this scope. Immutable.
* @type {?object}
*/
get org() {
return this.#org;
}
/**
* Project for this scope.
* @type {?object}
*/
get project() {
return this.#project;
}
/**
* Returns the JSON representation of this scope.
* @return {?object}
*/
toJSON() {
const { org, project } = this;
return { org, project };
}
}
export { Scope };
/**
* The scope service manages the current scope, making it available throughout
* the Ember application. Use `service.org` and `service.project` directly,
* rather than interacting with the internal `scope` member.
*
* @example
* // inject the scope service
* import { inject as service } from '@ember/service';
* class MyClass {
* @service scope;
* }
*
* // set an org, project, or both
* this.scope.org = {id: 1};
* this.scope.project = {id: 2};
*
* // retrieve the org and/or project
* const org = this.scope.org;
* const project = this.scope.project;
*/
export default class ScopeService extends Service {
/**
* The currently selected scope. This is an internal member only.
* @type {?Scope}
*/
@tracked scope = new Scope();
/**
* When setting org, `service.org = {...}`, a new scope is instantiated,
* since scopes are immutable.
* @type {?object}
*/
@computed('scope.org')
get org() {
return this.scope ? this.scope.org : null;
}
set org(org) {
this.scope = new Scope(org, this.project);
}
/**
* When setting project, `service.project = {...}`, a new scope is
* instantiated, since scopes are immutable.
* @type {?object}
*/
@computed('scope.project')
get project() {
return this.scope ? this.scope.project : null;
}
set project(project) {
this.scope = new Scope(this.org, project);
}
}
+130
View File
@@ -0,0 +1,130 @@
import Service from '@ember/service';
import { getOwner } from '@ember/application';
import { get } from '@ember/object';
/**
* A dead simple memory storage class used by `StorageService` when it
* is configued for memory-only.
*/
class MemoryStorage {
/**
* Internal storage object.
* @type {object}
* @private
*/
#storage = {};
/**
* Gets a value from storage.
* @param {string} key
* @return {any}
*/
getItem(key) {
return this.#storage[key];
}
/**
* Sets a value to storage.
* @param {string} key
*/
setItem(key, value) {
this.#storage[key] = value;
}
/**
* Removes a value from storage.
* @param {string} key
*/
removeItem(key) {
delete this.#storage[key];
}
}
/**
* The storage service provides a simple way to persist data in the browser.
* Most of the time localStorage is used. Appplications may specify in-memory
* storage via the config, for example:
*
* ENV.storage = {
* memory: true
* };
*
* @example
* import Route from '@ember/routing/route';
* import { inject as service } from '@ember/service';
* export default class ApplicationRoute extends Route {
* @service storage;
* afterModel() {
* this.storage.setItem('key', 'value');
* }
* }
*/
export default class StorageService extends Service {
/**
* @type {MemoryStorage}
* @private
*/
#memoryStorage = new MemoryStorage();
/**
* Gets the underlying storage mechanism.
* @return {Storage}
*/
get storage() {
// Get the application config to determine if memory storage is enabled
const config = getOwner(this).resolveRegistration('config:environment');
const inMemory = get(config, 'storage.memory');
// Get the global context object (window) via service lookup,
// because just reaching for a global is icky
const globalContext = getOwner(this).lookup('service:-document')
.documentElement.parentNode.defaultView;
// If memory storage enabled, return the memory storage instance,
// otherwise return localStorage.
return inMemory ? this.#memoryStorage : globalContext.localStorage;
}
/**
* Returns the storage namespace, which is the application's `modulePrefix`.
* @return {string}
*/
get namespace() {
const config = getOwner(this).resolveRegistration('config:environment');
return config.modulePrefix;
}
/**
* Returns a fully qualified key from the passed string. A fully qualified
* key includes the namespace.
* @param {string} key
* @return {string} fully qualified key
*/
qualifiedKey(key) {
return `${this.namespace}:${key}`;
}
/**
* Gets a value from storage.
* @param {string} key
* @return {any}
*/
getItem(key) {
const value = this.storage.getItem(this.qualifiedKey(key));
if (value) return JSON.parse(value);
}
/**
* Sets a value to storage.
* @param {string} key
*/
setItem(key, value) {
this.storage.setItem(this.qualifiedKey(key), JSON.stringify(value));
}
/**
* Removes a value from storage.
* @param {string} key
*/
removeItem(key) {
this.storage.removeItem(this.qualifiedKey(key));
}
}
+1
View File
@@ -0,0 +1 @@
export { default, Scope } from 'api/services/scope';
+1
View File
@@ -0,0 +1 @@
export { default } from 'api/services/storage';
+1
View File
@@ -38,6 +38,7 @@
"ember-cli-sri": "^2.1.1",
"ember-cli-template-lint": "^1.0.0-beta.3",
"ember-cli-uglify": "^3.0.0",
"ember-data": "^3.16.0",
"ember-disable-prototype-extensions": "^1.1.3",
"ember-export-application-global": "^2.0.1",
"ember-load-initializers": "^2.1.1",
@@ -0,0 +1,10 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default class ApplicationRoute extends Route {
@service scope;
afterModel() {
//console.log(this.scope);
}
}
@@ -1,3 +1,3 @@
<h2 id="title">Welcome to Ember</h2>
{{outlet}}
{{outlet}}
@@ -0,0 +1 @@
index
@@ -41,6 +41,10 @@ module.exports = function (environment) {
ENV.APP.rootElement = '#ember-testing';
ENV.APP.autoboot = false;
ENV.storage = {
memory: true,
};
}
if (environment === 'production') {
@@ -0,0 +1,68 @@
import RESTAdapter from '@ember-data/adapter/rest';
import ScopeAdapterMixin from 'api/mixins/scope-adapter';
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Mixin | scope-adapter', function (hooks) {
setupTest(hooks);
test('it does not interfere with basic URLs', function (assert) {
assert.expect(1);
//const service = this.owner.lookup('service:scope');
const ScopeAdapter = RESTAdapter.extend(ScopeAdapterMixin);
this.owner.register('adapter:scope', ScopeAdapter);
const adapter = this.owner.lookup('adapter:scope');
const url = adapter._buildURL('model', 1);
assert.equal(url, '/models/1');
});
test('it does not interfere with namespaced URLs', function (assert) {
assert.expect(1);
//const service = this.owner.lookup('service:scope');
const ScopeAdapter = RESTAdapter.extend(ScopeAdapterMixin, {
namespace: 'api/v1',
});
this.owner.register('adapter:scope', ScopeAdapter);
const adapter = this.owner.lookup('adapter:scope');
const url = adapter._buildURL('model', 1);
assert.equal(url, '/api/v1/models/1');
});
test('it adds a scope to basic URLs when scope is set', function (assert) {
assert.expect(4);
const service = this.owner.lookup('service:scope');
service.org = { id: 1 };
const ScopeAdapter = RESTAdapter.extend(ScopeAdapterMixin, {
namespace: 'api/v1',
});
this.owner.register('adapter:scope', ScopeAdapter);
const adapter = this.owner.lookup('adapter:scope');
let url = adapter._buildURL('model', 1);
assert.equal(url, '/api/v1/orgs/1/models/1');
service.project = { id: 2 };
url = adapter._buildURL('model', 1);
assert.equal(url, '/api/v1/orgs/1/projects/2/models/1');
service.org = { id: 'foobar' };
url = adapter._buildURL('model', 1);
assert.equal(url, '/api/v1/orgs/foobar/projects/2/models/1');
// can't have a project without an org
service.org = null;
url = adapter._buildURL('model', 1);
assert.equal(url, '/api/v1/models/1');
});
test('it does not add a project scope to basic URLs when includeProject is false', function (assert) {
assert.expect(1);
const service = this.owner.lookup('service:scope');
service.org = { id: 1 };
service.project = { id: 2 };
const ScopeAdapter = RESTAdapter.extend(ScopeAdapterMixin, {
namespace: 'api/v1',
includeProject: false,
});
this.owner.register('adapter:scope', ScopeAdapter);
const adapter = this.owner.lookup('adapter:scope');
let url = adapter._buildURL('model', 1);
assert.equal(url, '/api/v1/orgs/1/models/1');
});
});
@@ -0,0 +1,66 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { Scope } from '../../../services/scope';
module('Unit | Service | scope class', function (hooks) {
setupTest(hooks);
test('it provides a scope class', function (assert) {
assert.expect(1);
assert.ok(Scope);
});
test('it has initializable org and project members', function (assert) {
assert.expect(2);
const org = { id: 1 };
const project = { id: 2 };
const scope = new Scope(org, project);
assert.equal(scope.org.id, 1);
assert.equal(scope.project.id, 2);
});
test('it has a null project if org is null, even if project was initialized to non-null', function (assert) {
assert.expect(2);
const org = null;
const project = { id: 2 };
const scope = new Scope(org, project);
assert.notOk(scope.org);
assert.notOk(scope.project);
});
test('it serializes to JSON', function (assert) {
assert.expect(2);
let scope = new Scope({ id: 1 });
assert.deepEqual(scope.toJSON(), {
org: { id: 1 },
project: null,
});
scope = new Scope({ id: 1 }, { id: 2 });
assert.deepEqual(scope.toJSON(), {
org: { id: 1 },
project: { id: 2 },
});
});
});
module('Unit | Service | scope', function (hooks) {
setupTest(hooks);
test('it returns nulls when scope is not set', function (assert) {
assert.expect(2);
const service = this.owner.lookup('service:scope');
assert.equal(service.org, null);
assert.equal(service.project, null);
});
test('it aliases org and project to a scope instance', function (assert) {
assert.expect(2);
const service = this.owner.lookup('service:scope');
const org = { id: 1 };
const project = { id: 2 };
service.org = org;
service.project = project;
assert.equal(service.org, service.scope.org);
assert.equal(service.project, service.scope.project);
});
});
@@ -0,0 +1,38 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Service | storage', function (hooks) {
setupTest(hooks);
test('it can persist values', function (assert) {
assert.expect(1);
const service = this.owner.lookup('service:storage');
service.setItem('foo', 'bar');
assert.equal(service.getItem('foo'), 'bar');
});
test('it can remove persisted values', function (assert) {
assert.expect(2);
const service = this.owner.lookup('service:storage');
service.setItem('foo', 'bar');
assert.equal(service.getItem('foo'), 'bar');
service.removeItem('foo');
assert.notOk(service.getItem('foo'));
});
// NOTE: the following tests are order-dependent, so may be invalidated
// if we switch to random test ordering
test("it's in-memory storage is implicitly refreshed for every test case", function (assert) {
assert.expect(1);
const service = this.owner.lookup('service:storage');
service.setItem('foo', 'bar');
assert.equal(service.getItem('foo'), 'bar');
});
test("it's in-memory storage is implicitly refreshed for every test case", function (assert) {
assert.expect(1);
const service = this.owner.lookup('service:storage');
assert.notOk(service.getItem('foo'));
});
});
+2 -2
View File
@@ -3,8 +3,8 @@ import { action } from '@ember/object';
export default class IndexController extends Controller {
@action
selectChange(value) {
selectChange(/*value*/) {
// eslint-disable-next-line no-console
console.log(value);
//console.log(value);
}
}
+301 -10
View File
@@ -389,7 +389,7 @@
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-syntax-json-strings" "^7.8.0"
"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3":
"@babel/plugin-proposal-nullish-coalescing-operator@^7.4.4", "@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2"
integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==
@@ -431,7 +431,7 @@
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
"@babel/plugin-proposal-optional-chaining@^7.9.0":
"@babel/plugin-proposal-optional-chaining@^7.6.0", "@babel/plugin-proposal-optional-chaining@^7.9.0":
version "7.9.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58"
integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==
@@ -871,6 +871,15 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-typescript" "^7.2.0"
"@babel/plugin-transform-typescript@~7.8.0":
version "7.8.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.8.7.tgz#48bccff331108a7b3a28c3a4adc89e036dc3efda"
integrity sha512-7O0UsPQVNKqpHeHLpfvOG4uXmlw+MOxYvUv6Otc9uH5SYMIxvF6eBdjkWvC3f9G+VXe0RsNExyAQBeTRug/wqQ==
dependencies:
"@babel/helper-create-class-features-plugin" "^7.8.3"
"@babel/helper-plugin-utils" "^7.8.3"
"@babel/plugin-syntax-typescript" "^7.8.3"
"@babel/plugin-transform-unicode-regex@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad"
@@ -1112,7 +1121,7 @@
lodash "^4.17.13"
to-fast-properties "^2.0.0"
"@babel/types@^7.4.0", "@babel/types@^7.6.0", "@babel/types@^7.9.6":
"@babel/types@^7.4.0", "@babel/types@^7.6.0", "@babel/types@^7.7.2", "@babel/types@^7.9.6":
version "7.9.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.6.tgz#2c5502b427251e9de1bd2dff95add646d95cc9f7"
integrity sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==
@@ -1179,11 +1188,127 @@
resolved "https://registry.yarnpkg.com/@egoist/vue-to-react/-/vue-to-react-1.1.0.tgz#83c884b8608e8ee62e76c03e91ce9c26063a91ad"
integrity sha512-MwfwXHDh6ptZGLEtNLPXp2Wghteav7mzpT2Mcwl3NZWKF814i5hhHnNkVrcQQEuxUroSWQqzxLkMKSb+nhPang==
"@ember-data/adapter@3.18.0":
version "3.18.0"
resolved "https://registry.yarnpkg.com/@ember-data/adapter/-/adapter-3.18.0.tgz#0e25af9357d632bccb90df3109c679e922a64a25"
integrity sha512-fNFbnHF+QM9QAwd+AF131QRUM9P2W5mE5hycVyaHVfBPdNSDCJI85Ca+nwFF1GfwzImlUojUAyW5z+MzJCXhEw==
dependencies:
"@ember-data/private-build-infra" "3.18.0"
"@ember-data/store" "3.18.0"
"@ember/edition-utils" "^1.2.0"
ember-cli-babel "^7.18.0"
ember-cli-test-info "^1.0.0"
ember-cli-typescript "^3.1.3"
"@ember-data/canary-features@3.18.0":
version "3.18.0"
resolved "https://registry.yarnpkg.com/@ember-data/canary-features/-/canary-features-3.18.0.tgz#cc94867a8d6309d7dff48143c79497c43fd301d2"
integrity sha512-NRddFSlqdN5D86MKBgJ4MQzAzKHGQasDOWCEoGJHMF/Muh3I1NqAVAtI/I68wPr/k1j5hP1wMT6/tBsBMJXb/Q==
dependencies:
ember-cli-babel "^7.18.0"
ember-cli-typescript "^3.1.3"
"@ember-data/debug@3.18.0":
version "3.18.0"
resolved "https://registry.yarnpkg.com/@ember-data/debug/-/debug-3.18.0.tgz#3ce284ce89e83b05ceae81a5be03248a341591fb"
integrity sha512-9g/ghkq8SLmbOG0UUTyeX0ZAmQ3j3us2Ajkg8u3pVDKEpNdbQLhU5Npd9rf5gJe1TKmLcH7AcDpu85ZPLbb69w==
dependencies:
"@ember-data/private-build-infra" "3.18.0"
"@ember/edition-utils" "^1.2.0"
ember-cli-babel "^7.18.0"
ember-cli-test-info "^1.0.0"
ember-cli-typescript "^3.1.3"
"@ember-data/model@3.18.0":
version "3.18.0"
resolved "https://registry.yarnpkg.com/@ember-data/model/-/model-3.18.0.tgz#f2aebb34005dd2926cf11810d3b3cf7f2cc6c5ff"
integrity sha512-faFCpBl6VBzPl6tcJKi7FVEnC9xcx3qnle70tJWTAC++MK+GHlpQofNPWVu6cCebfDojHrLszfxjrDzmjvizqQ==
dependencies:
"@ember-data/canary-features" "3.18.0"
"@ember-data/private-build-infra" "3.18.0"
"@ember-data/store" "3.18.0"
"@ember/edition-utils" "^1.2.0"
ember-cli-babel "^7.18.0"
ember-cli-string-utils "^1.1.0"
ember-cli-test-info "^1.0.0"
ember-cli-typescript "^3.1.3"
ember-compatibility-helpers "^1.2.0"
inflection "1.12.0"
"@ember-data/private-build-infra@3.18.0":
version "3.18.0"
resolved "https://registry.yarnpkg.com/@ember-data/private-build-infra/-/private-build-infra-3.18.0.tgz#4420c375fa2462946ad6f9c62f73b79bba4d0e87"
integrity sha512-iQ4TzYdDCm0awv7S0Xg0AfQCDfwkl5pTCk7G8DfzbA3IIGJ0sFR19cq1IZkNKxqTUzU5ecb+394YybHjz81hMQ==
dependencies:
"@babel/plugin-transform-block-scoping" "^7.8.3"
"@ember-data/canary-features" "3.18.0"
"@ember/edition-utils" "^1.2.0"
babel-plugin-debug-macros "^0.3.3"
babel-plugin-filter-imports "^4.0.0"
babel6-plugin-strip-class-callcheck "^6.0.0"
broccoli-debug "^0.6.5"
broccoli-file-creator "^2.1.1"
broccoli-funnel "^2.0.2"
broccoli-merge-trees "^4.1.0"
broccoli-rollup "^4.1.1"
calculate-cache-key-for-tree "^2.0.0"
chalk "^3.0.0"
ember-cli-babel "^7.18.0"
ember-cli-path-utils "^1.0.0"
ember-cli-string-utils "^1.1.0"
ember-cli-typescript "^3.1.3"
ember-cli-version-checker "^4.1.0"
esm "^3.2.25"
git-repo-info "^2.1.1"
glob "^7.1.6"
npm-git-info "^1.0.3"
rimraf "^3.0.2"
rsvp "^4.8.5"
semver "^7.1.3"
silent-error "^1.1.1"
"@ember-data/record-data@3.18.0":
version "3.18.0"
resolved "https://registry.yarnpkg.com/@ember-data/record-data/-/record-data-3.18.0.tgz#c98e55e74950c3383080a5deb5b32a7210c2919e"
integrity sha512-WRrqBjFbD5tHeKnda+lvN9+3cPV/tJE0dkZIebheCvnkqueeGgp0+CW8ASp2iFySqRsBjlSxt1g46YrRdKVJtA==
dependencies:
"@ember-data/canary-features" "3.18.0"
"@ember-data/private-build-infra" "3.18.0"
"@ember-data/store" "3.18.0"
"@ember/edition-utils" "^1.2.0"
"@ember/ordered-set" "^2.0.3"
ember-cli-babel "^7.18.0"
ember-cli-test-info "^1.0.0"
ember-cli-typescript "^3.1.3"
"@ember-data/rfc395-data@^0.0.4":
version "0.0.4"
resolved "https://registry.yarnpkg.com/@ember-data/rfc395-data/-/rfc395-data-0.0.4.tgz#ecb86efdf5d7733a76ff14ea651a1b0ed1f8a843"
integrity sha512-tGRdvgC9/QMQSuSuJV45xoyhI0Pzjm7A9o/MVVA3HakXIImJbbzx/k/6dO9CUEQXIyS2y0fW6C1XaYOG7rY0FQ==
"@ember-data/serializer@3.18.0":
version "3.18.0"
resolved "https://registry.yarnpkg.com/@ember-data/serializer/-/serializer-3.18.0.tgz#d61fa2a11f6875f73bfdb492d316f052aa12b6ad"
integrity sha512-YBDHtGBxsHTIkk3c5ok3TrM3JCD2iW1n427Qe0v3/QMQCxX3eKT4rS8p0PP/orT5sUZ05zNOw6HlKfdEUKqAhA==
dependencies:
"@ember-data/private-build-infra" "3.18.0"
"@ember-data/store" "3.18.0"
ember-cli-babel "^7.18.0"
ember-cli-test-info "^1.0.0"
ember-cli-typescript "^3.1.3"
"@ember-data/store@3.18.0":
version "3.18.0"
resolved "https://registry.yarnpkg.com/@ember-data/store/-/store-3.18.0.tgz#04f7734a1f654c4eaf999f94f5a1451e763b7670"
integrity sha512-FBfXqRct3khpUsdusdFVFt8qNrWDPpqT/V0wFnwjSje5uX+DSwZ247SrtCYkwf6OziSzOfWR/w286O+0nawAyQ==
dependencies:
"@ember-data/canary-features" "3.18.0"
"@ember-data/private-build-infra" "3.18.0"
ember-cli-babel "^7.18.0"
ember-cli-path-utils "^1.0.0"
ember-cli-typescript "^3.1.3"
heimdalljs "^0.3.0"
"@ember/edition-utils@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@ember/edition-utils/-/edition-utils-1.2.0.tgz#a039f542dc14c8e8299c81cd5abba95e2459cfa6"
@@ -1202,6 +1327,14 @@
silent-error "^1.1.1"
util.promisify "^1.0.0"
"@ember/ordered-set@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@ember/ordered-set/-/ordered-set-2.0.3.tgz#2ac1ca73b3bd116063cae814898832ef434a57f9"
integrity sha512-F4yfVk6WMc4AUHxeZsC3CaKyTvO0qSZJy7WWHCFTlVDQw6vubn+FvnGdhzpN1F00EiXMI4Tv1tJdSquHcCnYrA==
dependencies:
ember-cli-babel "^6.16.0"
ember-compatibility-helpers "^1.1.1"
"@ember/test-helpers@^1.7.0", "@ember/test-helpers@^1.7.1":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@ember/test-helpers/-/test-helpers-1.7.1.tgz#cc22a954b3b46856518f034bd492a74e0482389f"
@@ -2073,6 +2206,11 @@
"@types/connect" "*"
"@types/node" "*"
"@types/broccoli-plugin@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@types/broccoli-plugin/-/broccoli-plugin-1.3.0.tgz#38f8462fecaebc4e09a32e4d4ed1b9808f75bbca"
integrity sha512-SLk4/hFc2kGvgwNFrpn2O1juxFOllcHAywvlo7VwxfExLzoz1GGJ0oIZCwj5fwSpvHw4AWpZjJ1fUvb62PDayQ==
"@types/chai-as-promised@^7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.2.tgz#2f564420e81eaf8650169e5a3a6b93e096e5068b"
@@ -2097,6 +2235,11 @@
dependencies:
"@types/node" "*"
"@types/estree@*":
version "0.0.44"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.44.tgz#980cc5a29a3ef3bea6ff1f7d021047d7ea575e21"
integrity sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g==
"@types/events@*":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
@@ -3565,6 +3708,14 @@ babel-plugin-filter-imports@^3.0.0:
"@babel/types" "^7.4.0"
lodash "^4.17.11"
babel-plugin-filter-imports@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/babel-plugin-filter-imports/-/babel-plugin-filter-imports-4.0.0.tgz#068f8da15236a96a9602c36dc6f4a6eeca70a4f4"
integrity sha512-jDLlxI8QnfKd7PtieH6pl4tZJzymzfCDCPGdTq/grgbiYAikwDPp/oL0IlFJn0HQjLpcLkyYhPKkUVneRESw5w==
dependencies:
"@babel/types" "^7.7.2"
lodash "^4.17.15"
babel-plugin-htmlbars-inline-precompile@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-1.0.0.tgz#a9d2f6eaad8a3f3d361602de593a8cbef8179c22"
@@ -4130,6 +4281,11 @@ babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0:
lodash "^4.17.4"
to-fast-properties "^1.0.3"
babel6-plugin-strip-class-callcheck@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/babel6-plugin-strip-class-callcheck/-/babel6-plugin-strip-class-callcheck-6.0.0.tgz#de841c1abebbd39f78de0affb2c9a52ee228fddf"
integrity sha1-3oQcGr6705943gr/ssmlLuIo/d8=
babylon@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
@@ -4679,7 +4835,7 @@ broccoli-merge-trees@^3.0.0, broccoli-merge-trees@^3.0.1, broccoli-merge-trees@^
broccoli-plugin "^1.3.0"
merge-trees "^2.0.0"
broccoli-merge-trees@^4.2.0:
broccoli-merge-trees@^4.1.0, broccoli-merge-trees@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/broccoli-merge-trees/-/broccoli-merge-trees-4.2.0.tgz#692d3c163ecea08c5714a9434d664e628919f47c"
integrity sha512-nTrQe5AQtCrW4enLRvbD/vTLHqyW2tz+vsLXQe4IEaUhepuMGVKJJr+I8n34Vu6fPjmPLwTjzNC8izMIDMtHPw==
@@ -4806,7 +4962,7 @@ broccoli-plugin@^1.0.0, broccoli-plugin@^1.1.0, broccoli-plugin@^1.2.0, broccoli
rimraf "^2.3.4"
symlink-or-copy "^1.1.8"
broccoli-plugin@^2.1.0:
broccoli-plugin@^2.0.0, broccoli-plugin@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/broccoli-plugin/-/broccoli-plugin-2.1.0.tgz#2fab6c578219cfcc64f773e9616073313fc8b334"
integrity sha512-ElE4caljW4slapyEhSD9jU9Uayc8SoSABWdmY9SqbV8DHNxU6xg1jJsPcMm+cXOvggR3+G+OXAYQeFjWVnznaw==
@@ -4842,6 +4998,21 @@ broccoli-plugin@^4.0.1, broccoli-plugin@^4.0.2:
rimraf "^3.0.0"
symlink-or-copy "^1.3.0"
broccoli-rollup@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/broccoli-rollup/-/broccoli-rollup-4.1.1.tgz#7531a24d88ddab9f1bace1c6ee6e6ca74a38d36f"
integrity sha512-hkp0dB5chiemi32t6hLe5bJvxuTOm1TU+SryFlZIs95KT9+94uj0C8w6k6CsZ2HuIdIZg6D252t4gwOlcTXrpA==
dependencies:
"@types/broccoli-plugin" "^1.3.0"
broccoli-plugin "^2.0.0"
fs-tree-diff "^2.0.1"
heimdalljs "^0.2.6"
node-modules-path "^1.0.1"
rollup "^1.12.0"
rollup-pluginutils "^2.8.1"
symlink-or-copy "^1.2.0"
walk-sync "^1.1.3"
broccoli-sass-source-maps@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/broccoli-sass-source-maps/-/broccoli-sass-source-maps-4.0.0.tgz#1ee4c10a810b10955b0502e28f85ab672f5961a2"
@@ -7026,7 +7197,7 @@ ember-cli-sri@^2.1.1:
dependencies:
broccoli-sri-hash "^2.1.0"
ember-cli-string-utils@^1.1.0:
ember-cli-string-utils@^1.0.0, ember-cli-string-utils@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/ember-cli-string-utils/-/ember-cli-string-utils-1.1.0.tgz#39b677fc2805f55173735376fcef278eaa4452a1"
integrity sha1-ObZ3/CgF9VFzc1N2/O8njqpEUqE=
@@ -7048,6 +7219,13 @@ ember-cli-template-lint@^1.0.0-beta.3:
strip-ansi "^4.0.0"
walk-sync "^1.1.3"
ember-cli-test-info@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ember-cli-test-info/-/ember-cli-test-info-1.0.0.tgz#ed4e960f249e97523cf891e4aed2072ce84577b4"
integrity sha1-7U6WDySel1I8+JHkrtIHLOhFd7Q=
dependencies:
ember-cli-string-utils "^1.0.0"
ember-cli-test-loader@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/ember-cli-test-loader/-/ember-cli-test-loader-2.2.0.tgz#3fb8d5d1357e4460d3f0a092f5375e71b6f7c243"
@@ -7090,6 +7268,26 @@ ember-cli-typescript@^2.0.2:
stagehand "^1.0.0"
walk-sync "^1.0.0"
ember-cli-typescript@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/ember-cli-typescript/-/ember-cli-typescript-3.1.3.tgz#a2c7ec6a8a5e57c38eb52d83e36d8e18c7071e60"
integrity sha512-bFi15H60L9TLYfn9XUzi+RAP1gTWHFtVdSy9IHvxXHlCvTlFZ+2rfuugr/f8reQLz9gvJccKc5TyRD7v+uhx0Q==
dependencies:
"@babel/plugin-proposal-nullish-coalescing-operator" "^7.4.4"
"@babel/plugin-proposal-optional-chaining" "^7.6.0"
"@babel/plugin-transform-typescript" "~7.8.0"
ansi-to-html "^0.6.6"
broccoli-stew "^3.0.0"
debug "^4.0.0"
ember-cli-babel-plugin-helpers "^1.0.0"
execa "^3.0.0"
fs-extra "^8.0.0"
resolve "^1.5.0"
rsvp "^4.8.1"
semver "^6.3.0"
stagehand "^1.0.0"
walk-sync "^2.0.0"
ember-cli-uglify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ember-cli-uglify/-/ember-cli-uglify-3.0.0.tgz#8819665b2cc5fe70e3ba9fe7a94645209bc42fd6"
@@ -7227,7 +7425,7 @@ ember-cli@~3.16.1:
watch-detector "^1.0.0"
yam "^1.0.0"
ember-compatibility-helpers@^1.1.2:
ember-compatibility-helpers@^1.1.1, ember-compatibility-helpers@^1.1.2, ember-compatibility-helpers@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ember-compatibility-helpers/-/ember-compatibility-helpers-1.2.1.tgz#87c92c4303f990ff455c28ca39fb3ee11441aa16"
integrity sha512-6wzYvnhg1ihQUT5yGqnLtleq3Nv5KNv79WhrEuNU9SwR4uIxCO+KpyC7r3d5VI0EM7/Nmv9Nd0yTkzmTMdVG1A==
@@ -7236,6 +7434,26 @@ ember-compatibility-helpers@^1.1.2:
ember-cli-version-checker "^2.1.1"
semver "^5.4.1"
ember-data@^3.16.0:
version "3.18.0"
resolved "https://registry.yarnpkg.com/ember-data/-/ember-data-3.18.0.tgz#e7c27e311b62f986e55075dc61ca581e7c74a4d3"
integrity sha512-8P5A6Il7X0QQJRJA4OXdX7yOc8A5/befxfbFBNTzi2Ptq+m2zNBfdW98lfPb9M6h3GMjwsZCmVlNVK6ic8dmMA==
dependencies:
"@ember-data/adapter" "3.18.0"
"@ember-data/debug" "3.18.0"
"@ember-data/model" "3.18.0"
"@ember-data/private-build-infra" "3.18.0"
"@ember-data/record-data" "3.18.0"
"@ember-data/serializer" "3.18.0"
"@ember-data/store" "3.18.0"
"@ember/edition-utils" "^1.2.0"
"@ember/ordered-set" "^2.0.3"
"@glimmer/env" "^0.1.7"
broccoli-merge-trees "^4.1.0"
ember-cli-babel "^7.18.0"
ember-cli-typescript "^3.1.3"
ember-inflector "^3.0.1"
ember-disable-prototype-extensions@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/ember-disable-prototype-extensions/-/ember-disable-prototype-extensions-1.1.3.tgz#1969135217654b5e278f9fe2d9d4e49b5720329e"
@@ -7246,6 +7464,13 @@ ember-export-application-global@^2.0.1:
resolved "https://registry.yarnpkg.com/ember-export-application-global/-/ember-export-application-global-2.0.1.tgz#b120a70e322ab208defc9e2daebe8d0dfc2dcd46"
integrity sha512-B7wiurPgsxsSGzJuPFkpBWnaeuCu2PGpG2BjyrfA1VcL7//o+5RSnZqiCEY326y7qmxb2GoCgo0ft03KBU0rRw==
ember-inflector@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ember-inflector/-/ember-inflector-3.0.1.tgz#04be6df4d7e4000f6d6bd70787cdc995f77be4ab"
integrity sha512-fngrwMsnhkBt51KZgwNwQYxgURwV4lxtoHdjxf7RueGZ5zM7frJLevhHw7pbQNGqXZ3N+MRkhfNOLkdDK9kFdA==
dependencies:
ember-cli-babel "^6.6.0"
ember-inline-svg@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ember-inline-svg/-/ember-inline-svg-1.0.0.tgz#d8c243667a9277b0fef06eb63e4f1c4cf1e8013e"
@@ -7890,7 +8115,7 @@ eslint@^5.6.0:
table "^5.2.3"
text-table "^0.2.0"
esm@^3.2.4:
esm@^3.2.25, esm@^3.2.4:
version "3.2.25"
resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==
@@ -7951,6 +8176,11 @@ estraverse@^5.1.0:
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642"
integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==
estree-walker@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
@@ -8032,6 +8262,22 @@ execa@^2.0.0:
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
execa@^3.0.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89"
integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==
dependencies:
cross-spawn "^7.0.0"
get-stream "^5.0.0"
human-signals "^1.1.1"
is-stream "^2.0.0"
merge-stream "^2.0.0"
npm-run-path "^4.0.0"
onetime "^5.1.0"
p-finally "^2.0.0"
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
exists-sync@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/exists-sync/-/exists-sync-0.0.4.tgz#9744c2c428cc03b01060db454d4b12f0ef3c8879"
@@ -9486,6 +9732,13 @@ heimdalljs@^0.2.0, heimdalljs@^0.2.1, heimdalljs@^0.2.3, heimdalljs@^0.2.5, heim
dependencies:
rsvp "~3.2.1"
heimdalljs@^0.3.0:
version "0.3.3"
resolved "https://registry.yarnpkg.com/heimdalljs/-/heimdalljs-0.3.3.tgz#e92d2c6f77fd46d5bf50b610d28ad31755054d0b"
integrity sha1-6S0sb3f9RtW/ULYQ0orTF1UFTQs=
dependencies:
rsvp "~3.2.1"
highlight.js@~9.13.0:
version "9.13.1"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e"
@@ -9690,6 +9943,11 @@ https@^1.0.0:
resolved "https://registry.yarnpkg.com/https/-/https-1.0.0.tgz#3c37c7ae1a8eeb966904a2ad1e975a194b7ed3a4"
integrity sha1-PDfHrhqO65ZpBKKtHpdaGUt+06Q=
human-signals@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -9794,7 +10052,7 @@ infer-owner@^1.0.3, infer-owner@^1.0.4:
resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==
inflection@^1.12.0:
inflection@1.12.0, inflection@^1.12.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416"
integrity sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=
@@ -11936,7 +12194,7 @@ node-libs-browser@^2.0.0, node-libs-browser@^2.2.1:
util "^0.11.0"
vm-browserify "^1.0.1"
node-modules-path@^1.0.0:
node-modules-path@^1.0.0, node-modules-path@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/node-modules-path/-/node-modules-path-1.0.2.tgz#e3acede9b7baf4bc336e3496b58e5b40d517056e"
integrity sha512-6Gbjq+d7uhkO7epaKi5DNgUJn7H0gEyA4Jg0Mo1uQOi3Rk50G83LtmhhFyw0LxnAFhtlspkiiw52ISP13qzcBg==
@@ -12023,6 +12281,11 @@ normalize-url@2.0.1:
query-string "^5.0.1"
sort-keys "^2.0.0"
npm-git-info@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/npm-git-info/-/npm-git-info-1.0.3.tgz#a933c42ec321e80d3646e0d6e844afe94630e1d5"
integrity sha1-qTPELsMh6A02RuDW6ESv6UYw4dU=
npm-normalize-package-bin@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2"
@@ -12052,6 +12315,13 @@ npm-run-path@^3.0.0:
dependencies:
path-key "^3.0.0"
npm-run-path@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
dependencies:
path-key "^3.0.0"
npmlog@^4.0.0, npmlog@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
@@ -14251,6 +14521,22 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
rollup-pluginutils@^2.8.1:
version "2.8.2"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"
integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==
dependencies:
estree-walker "^0.6.1"
rollup@^1.12.0:
version "1.32.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.32.1.tgz#4480e52d9d9e2ae4b46ba0d9ddeaf3163940f9c4"
integrity sha512-/2HA0Ec70TvQnXdzynFffkjA6XN+1e2pEv/uKS5Ulca40g2L7KuOE3riasHoNVHOsFD5KKZgDsMk1CP3Tw9s+A==
dependencies:
"@types/estree" "*"
"@types/node" "*"
acorn "^7.1.0"
rsvp@^3.0.14, rsvp@^3.0.17, rsvp@^3.0.18, rsvp@^3.0.21, rsvp@^3.0.6, rsvp@^3.1.0:
version "3.6.2"
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a"
@@ -14443,6 +14729,11 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.1.3:
version "7.3.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"