feat: Add mirage to desktop

This commit is contained in:
Susmitha Girumala committed 2020-11-09 17:46:46 -05:00
1 parent 36d4c5bca4
commit 9f7469992a
46 files changed
+892 -5

No files matched your search

+14 -1
View File
@@ -1,8 +1,21 @@
import Route from '@ember/routing/route';
import { A } from '@ember/array';
export default class ScopesRoute extends Route {
// =methods
/**
* Attempt to load all scopes from the API. This is allowed
* to fail, since in some cases the user may not have permission to read a
* scope directly, but may have permission to read resources under it.
* If scopes fails to load, we still proceed using an empty array.
* @param {object} params
* @param {string} params.scope_id
* @return {Promise{[ScopeModel]}}
*/
model() {
// return this.store.findAll('scope');
// NOTE: In the absence of a `scope_id` query parameter, this endpoint is
// expected to default to the global scope, thus returning org scopes.
return this.store.findAll('scope').catch(() => A([]));
}
}
+1
View File
@@ -1,3 +1,4 @@
I'm in scopes
{{@model}}
{{outlet}}
+2 -2
View File
@@ -7,8 +7,8 @@ module.exports = function (environment) {
let ENV = {
modulePrefix: 'desktop',
environment,
rootURL: process.env.EMBER_CLI_ELECTRON ? '' : '/',
locationType: process.env.EMBER_CLI_ELECTRON ? 'hash' : 'auto',
rootURL: process.env.EMBER_CLI_ELECTRON ? '' : process.env.EMBER_CLI_ELECTRON ? '' : '/',
locationType: process.env.EMBER_CLI_ELECTRON ? 'hash' : process.env.EMBER_CLI_ELECTRON ? 'hash' : 'auto',
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
+149
View File
@@ -0,0 +1,149 @@
import Ember from 'ember';
import config from '../config/environment';
import { authHandler, deauthHandler } from './route-handlers/auth';
import { pickRandomStatusString } from './factories/session';
import { Response } from 'miragejs';
export default function() {
// make this `http://localhost:8080`, for example, if your API is on a different server
// this.urlPrefix = '';
// make this `/api`, for example, if your API is namespaced
this.namespace = config.api.namespace;
// Allow any configured API host to passthrough, which is useful in
// development for testing against a locally running backend.
if (config.api.host) this.passthrough(`${config.api.host}/**`);
this.passthrough();
// Scope resources
this.get('/scopes', ({ scopes }, { queryParams: { scope_id } }) => {
// Default parent scope is global
if (!scope_id) scope_id = 'global';
const results = scopes.where(scope => {
return scope.scope ? scope.scope.id === scope_id : false
});
return results;
});
this.get('/scopes/:id');
// Auth & IAM resources
this.get('/auth-methods', ({ authMethods }, { queryParams: { scope_id: scopeId } }) => {
return authMethods.where({ scopeId });
});
this.get('/auth-methods/:id');
// Auth Method Accounts
this.get('/accounts', ({ accounts }, { queryParams: { auth_method_id: authMethodId } }) => {
return accounts.where({ authMethodId });
});
this.get('/accounts/:id');
this.post('/accounts/:idMethod', function ({ accounts }, { params: { idMethod } }) {
const attrs = this.normalizedRequestAttrs();
const id = idMethod.split(':')[0];
const method = idMethod.split(':')[1];
const account = accounts.find(id);
const updatedAttrs = {
version: attrs.version,
attributes: account.attributes
};
// Set new password
if (method === 'change-password') {
updatedAttrs.attributes.password = attrs.new_password;
}
return account.update(updatedAttrs);
});
// Authenticate/deauthenticate routes
this.post('/auth-methods/:id_method', authHandler);
this.post('/scopes/:id_method', deauthHandler);
// Other resources
// host-catalog
this.get('/host-catalogs', ({ hostCatalogs }, { queryParams: { scope_id: scopeId } }) => {
return hostCatalogs.where({ scopeId });
});
this.get('/host-catalogs/:id');
// host
this.get('/hosts', function ({ hosts }, { queryParams: { host_catalog_id: hostCatalogId } }) {
return hosts.where({ hostCatalogId });
});
this.get('/hosts/:id');
// host-set
this.get('/host-sets', function ({ hostSets }, { queryParams: { host_catalog_id: hostCatalogId } }) {
return hostSets.where({ hostCatalogId });
});
this.get('/host-sets/:id');
// target
this.get('/targets', function ({ targets }, { queryParams: { scope_id } }) {
return targets.where(target => target.scopeId === scope_id);
});
this.get('/targets/:id');
// session
this.get('/sessions', function ({ sessions }, { queryParams: { scope_id } }) {
// To simulate changes to `session.status` that may occur in the backend,
// we quietly randomize the value of the field on GET.
// But only if not in testing mode.
// In tests, we need deterministic statuses.
if (!Ember.testing) {
sessions.where(session => session.scopeId === scope_id)
.models
.forEach(session => session.update({
status: pickRandomStatusString()
}));
}
return sessions.where(session => session.scopeId === scope_id)
});
this.get('/sessions/:id', function ({ sessions }, { params: { id } }) {
const session = sessions.find(id);
return session.update({
status: pickRandomStatusString()
});
});
this.post('/sessions/:idMethod', function ({ sessions }, { params: { idMethod } }) {
const attrs = this.normalizedRequestAttrs();
const id = idMethod.split(':')[0];
const method = idMethod.split(':')[1];
const session = sessions.find(id);
const updatedAttrs = {
version: attrs.version
};
if (method === 'cancel') {
updatedAttrs.status = 'canceling';
}
return session.update(updatedAttrs);
});
// auth-tokens
this.get('/auth-tokens/:id', function () {
// Set to 401 or 404 to simulate token invalidation, which will
// cause the session to fail restoration and logout the user.
return new Response(200);
});
/* Uncomment the following line and the Response import above
* Then change the response code to simulate error responses.
* this.get('/scopes', () => new Response(505));
*
* Update error payload to simulate specific error responses.
*/
// this.get('/host-catalogs', () => new Response(404));
// this.get('/scopes/:id', () => new Response(505, {}, {
// errors: [{
// status: 505,
// message: 'HTTP version not supported.',
// }]
// }));
}
+3
View File
@@ -0,0 +1,3 @@
import factory from '../generated/factories/account';
export default factory.extend();
@@ -0,0 +1,20 @@
import factory from '../generated/factories/auth-method';
import { trait } from 'ember-cli-mirage';
export default factory.extend({
id: (i) => `auth-method-id-${i}`,
/**
* Adds accounts (with associated users) to auth method.
*/
withAccountsAndUsers: trait({
afterCreate(authMethod, server) {
const { scope } = authMethod;
server.createList('user', 5, { scope }).map(user => {
const { id } = server.create('account', { scope, authMethod });
user.update({ accountIds: [id] });
});
}
})
});
@@ -0,0 +1,14 @@
import factory from '../generated/factories/host-catalog';
import { trait } from 'ember-cli-mirage';
export default factory.extend({
withChildren: trait({
afterCreate(hostCatalog, server) {
const { scope } = hostCatalog;
const hosts = server.createList('host', 10, { scope, hostCatalog });
server.createList('host-set', 3, { scope, hostCatalog, hosts });
}
})
});
+5
View File
@@ -0,0 +1,5 @@
import factory from '../generated/factories/host-set';
export default factory.extend({
id: (i) => `host-set-id-${i}`
});
+5
View File
@@ -0,0 +1,5 @@
import factory from '../generated/factories/host';
export default factory.extend({
id: (i) => `host-id-${i}`
});
+35
View File
@@ -0,0 +1,35 @@
import factory from '../generated/factories/scope';
import { trait } from 'ember-cli-mirage';
export default factory.extend({
type: 'global',
/**
* Generates realistic-ish IDs while still being deterministic.
*/
id(i) {
let id = 'tv6fv3yz8p';
if (i % 3) id = 'xk1i4dk50v';
if (i % 2) id = 's9n819zgko';
return `s_${id}${i}`;
},
withChildren: trait({
afterCreate(record, server) {
if (record.type === 'global') {
server.createList('scope', 1, {
type: 'org',
scope: { id: record.id, type: record.type }
}, 'withChildren');
}
if (record.type === 'org') {
server.createList('scope', 3, {
type: 'project',
scope: { id: record.id, type: record.type }
});
}
}
})
});
+46
View File
@@ -0,0 +1,46 @@
import factory from '../generated/factories/target';
import { trait } from 'ember-cli-mirage';
import { random } from 'faker';
const statusStrings = [
'pending',
'active',
'canceling',
'terminated'
];
export const pickRandomStatusString = () => random.arrayElement(statusStrings);
export default factory.extend({
id: (i) => `session-id-${i}`,
status: pickRandomStatusString,
/**
* Sessions generated with this trait will automatically form
* associations to user, target, host set, and host (if available) within
* the current scope.
*/
withAssociations: trait({
afterCreate(record, server) {
const scopeId = record.scopeId;
if (record.scope.type === 'project') {
const orgScopeId = record.scope?.scope.id;
const user =
server.schema.users.where({ scopeId: orgScopeId }).models[0];
if (user) record.update({ user });
}
const target = server.schema.targets.where({ scopeId }).models[0];
if (target) {
record.update({ target });
const hostSet = target.hostSets.models[0];
if (hostSet) {
record.update({ hostSet });
const host = hostSet.hosts.models[0];
if (host) record.update({ host });
}
}
}
})
});
+46
View File
@@ -0,0 +1,46 @@
import factory from '../generated/factories/target';
import { trait } from 'ember-cli-mirage';
import { random } from 'faker';
const randomBoolean = (chance = 0.5) => Math.random() < chance;
const hostSetChance = 0.3;
export default factory.extend({
/**
* -1 means "unlimited" and we want to generate these on occassion.
*/
session_connection_limit: () => random.arrayElement([-1, random.number()]),
/**
* Generates attributes fields by type.
*/
afterCreate(target) {
if (target.type === 'tcp') {
target.update({
attributes: {
default_port: random.number()
}
});
}
},
/**
* Randomly selects existing host sets to assign to target.
*/
withRandomHostSets: trait({
afterCreate(target, server) {
let randomlySelectedHostSets;
randomlySelectedHostSets = server.schema.hostSets
// BLERG: fun fact, for no reason at all, the element passed
// into a where function is not a full model instance, as you might
// expect at this level of abstraction, but appears to be a serialized
// representation of the model instance. It's very confusing since
// the result set of `where` _is a collection of model instances_.
.where(hostSet => hostSet.scopeId === target.scope.id).models
.filter(() => randomBoolean(hostSetChance));
target.update({hostSets: randomlySelectedHostSets});
}
})
});
+5
View File
@@ -0,0 +1,5 @@
import factory from '../generated/factories/user';
export default factory.extend({
id: (i) => `user-${i}`
});
@@ -0,0 +1,17 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedAccountFactory
*/
export default Factory.extend({
type: 'password',
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
version: () => random.number(),
attributes: () => ({ login_name: random.words() }),
});
@@ -0,0 +1,16 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedAuthMethodModelFactory
*/
export default Factory.extend({
type: 'password',
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
disabled: () => random.boolean(),
});
@@ -0,0 +1,16 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedHostCatalogModelFactory
*/
export default Factory.extend({
type: 'static',
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
disabled: () => random.boolean(),
});
@@ -0,0 +1,17 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedHostSetModelFactory
*/
export default Factory.extend({
type: 'static',
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
version: () => random.number(),
size: () => random.number(),
});
@@ -0,0 +1,17 @@
import { Factory } from 'ember-cli-mirage';
import { random, date, internet } from 'faker';
/**
* GeneratedHostModelFactory
*/
export default Factory.extend({
type: 'static',
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
version: () => random.number(),
attributes: () => { return { address: internet.ipv6() } },
});
@@ -0,0 +1,15 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedOrgModelFactory
*
*/
export default Factory.extend({
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
});
@@ -0,0 +1,16 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedProjectModelFactory
* Project contains all fields related to a Project resource
*/
export default Factory.extend({
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
disabled: () => random.boolean(),
});
@@ -0,0 +1,15 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedScopeModelFactory
*/
export default Factory.extend({
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
disabled: () => random.boolean(),
});
@@ -0,0 +1,14 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedSessionModelFactory
*/
export default Factory.extend({
type: 'tcp',
created_time: () => date.recent(),
updated_time: () => date.recent(),
version: () => random.number(),
});
@@ -0,0 +1,19 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedTargetModelFactory
*/
export default Factory.extend({
type: 'tcp',
name: () => random.words(),
session_max_seconds: () => random.number(),
session_connection_limit: () => random.number(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
disabled: () => random.boolean(),
version: () => random.number(),
});
@@ -0,0 +1,16 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedUserModelFactory
* User contains all fields related to a User resource
*/
export default Factory.extend({
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
disabled: () => random.boolean(),
});
+8
View File
@@ -0,0 +1,8 @@
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
authMethod: belongsTo()
});
+7
View File
@@ -0,0 +1,7 @@
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo()
});
+8
View File
@@ -0,0 +1,8 @@
import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
hostSets: hasMany()
});
+10
View File
@@ -0,0 +1,10 @@
import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
target: belongsTo(),
hostCatalog: belongsTo(),
hosts: hasMany()
});
+8
View File
@@ -0,0 +1,8 @@
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
hostCatalog: belongsTo()
});
+11
View File
@@ -0,0 +1,11 @@
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
user: belongsTo(),
target: belongsTo(),
host: belongsTo(),
hostSet: belongsTo(),
});
+8
View File
@@ -0,0 +1,8 @@
import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
hostSets: hasMany()
});
+8
View File
@@ -0,0 +1,8 @@
import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
accounts: hasMany('account')
});
+53
View File
@@ -0,0 +1,53 @@
// import config from '../../config/environment';
// import { v1 } from 'ember-uuid';
import { Response } from 'miragejs';
// /**
// * Simulates a cookie-based response to authentication requests. Any user/pass
// * values are allowed *except* when `username` equals `error`, in which case a
// * simulated error response is returned.
// * @param {object} schema
// * @param {object} request
// * @return {Response}
// */
// export default function authenticateHandler(schema, request) {
// const payload = JSON.parse(request.requestBody);
// if (payload.credentials.login_name === 'error') {
// return new Response(400);
// } else {
// const cookieName = config.auth.passwordCookieName;
// const cookieValue = v1();
// const cookiePath = config.rootURL;
// document.cookie = `${cookieName}=${cookieValue};path=${cookiePath}`;
// return new Response(200);
// }
// }
export function authHandler({ scopes, authMethods }, request) {
const payload = JSON.parse(request.requestBody);
if (payload.credentials.login_name === 'error') {
return new Response(400);
} else {
const id = request.params.id_method.split(':')[0];
const authMethod = authMethods.find(id);
const scope = scopes.find(authMethod.scopeId);
const scopeAttrs =
this.serialize(scopes.find(scope.id));
return new Response(200, {}, {
scope: scopeAttrs,
id: 'token123',
token: 'thetokenstring',
account_id: '1',
user_id: 'user123',
auth_method_id: 'authmethod123',
created_time: '',
updated_time: '',
last_used_time: '',
expiration_time: ''
});
}
}
export function deauthHandler() {
return new Response(200);
}
+27
View File
@@ -0,0 +1,27 @@
export default function(server) {
// Scope resources
// creates a global scope
const globalScope = server.create('scope', {
id: 'global',
type: 'global',
name: 'Global'
});
const orgScope = server.createList('scope', 3, {
type: 'org',
scope: { id: globalScope.id, type: globalScope.type }
}, 'withChildren')[0];
// Auth
server.createList('auth-method', 1, { scope: globalScope }, 'withAccountsAndUsers');
server.createList('auth-method', 3, { scope: orgScope }, 'withAccountsAndUsers');
// Other resources
server.schema.scopes.where({type: 'project'}).models.forEach(scope => {
server.createList('host-catalog', 2, { scope }, 'withChildren');
server.createList('target', 2, { scope }, 'withRandomHostSets');
server.createList('session', 4, { scope }, 'withAssociations');
});
}
+12
View File
@@ -0,0 +1,12 @@
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
modelName: 'account',
_hashForModel(model) {
const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments);
json.auth_method_id = model.authMethodId;
delete json.attributes?.password;
return json;
}
});
@@ -0,0 +1,84 @@
import { RestSerializer } from 'ember-cli-mirage';
import { underscore } from '@ember/string';
import { isArray } from '@ember/array';
/**
* Manages serialization/normalization of data into and out of the mock API.
*/
export default RestSerializer.extend({
// =attributes
/**
* False to disable automatically rooting payloads under a key.
* This is handled by the `serialize()` method, see below.
* @type {boolean}
*/
root: false,
/**
* @type {boolean}
*/
embed: true,
// =methods
/**
* Generates an underscored key for the attribute.
* @param {string} attr
* @return {string}
*/
keyForAttribute(attr) {
return underscore(attr);
},
keyForRelationshipId(relationshipName) {
return `${this._container.inflector.singularize(
underscore(relationshipName)
)}_id`;
},
/**
* Serialize items into a root key `items`.
* @return {object}
*/
serialize() {
let json = RestSerializer.prototype.serialize.apply(this, arguments);
// If array, root it under a standard `items` key
if (isArray(json)) json = {items: json};
return json;
},
/**
* Serialize scope into the payload, if set.
* @param {object} model
* @return {object}
*/
_hashForModel(model) {
const json = RestSerializer.prototype._hashForModel.apply(this, arguments);
if (model.scope) {
json.scope = {
id: model.scope.id,
type: model.scope.type
};
}
return json;
},
/**
* Adds a root to the payload, since `root: false` doesn't appear to affect
* the normalize method.
* @param {object} json
* @return {object}
*/
normalize(json) {
let newJSON = json;
if (this.modelName) {
newJSON = {};
newJSON[this.modelName] = json;
}
const value = RestSerializer.prototype.normalize.apply(this, [newJSON]);
return value;
}
});
@@ -0,0 +1,5 @@
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
modelName: 'auth-method'
});
@@ -0,0 +1,5 @@
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
modelName: 'host-catalog',
});
+22
View File
@@ -0,0 +1,22 @@
import ApplicationSerializer from './application';
import { underscore } from '@ember/string';
export default ApplicationSerializer.extend({
modelName: 'host-set',
keyForRelationshipIds(relationshipName) {
return `${this._container.inflector.singularize(
underscore(relationshipName)
)}_ids`;
},
_hashForModel(model) {
const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments);
json.host_catalog_id = model.hostCatalogId;
if (model.hostIds?.length) {
json.host_ids = model.hostIds;
}
return json;
}
});
+5
View File
@@ -0,0 +1,5 @@
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
modelName: 'host'
});
+5
View File
@@ -0,0 +1,5 @@
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
modelName: 'scope'
});
+22
View File
@@ -0,0 +1,22 @@
import ApplicationSerializer from './application';
import { underscore } from '@ember/string';
export default ApplicationSerializer.extend({
modelName: 'session',
keyForRelationshipId(relationshipName) {
return `${this._container.inflector.singularize(
underscore(relationshipName)
)}_id`;
},
_hashForModel(model) {
const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments);
json.user_id = model.userId;
json.target_id = model.targetId;
json.host_set_id = model.hostSetId;
json.host_id = model.hostId;
return json;
}
});
+26
View File
@@ -0,0 +1,26 @@
import ApplicationSerializer from './application';
import { underscore } from '@ember/string';
export default ApplicationSerializer.extend({
modelName: 'target',
keyForRelationshipIds(relationshipName) {
return `${this._container.inflector.singularize(
underscore(relationshipName)
)}_ids`;
},
_hashForModel(model) {
const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments);
const { hostSets } = this.schema;
if (model.hostSetIds?.length) {
json.host_sets = model.hostSetIds.map(host_set_id => {
const hostSet = hostSets.find(host_set_id);
const host_catalog_id = hostSet?.hostCatalog?.id;
return { id: host_set_id, host_catalog_id };
});
}
return json;
}
});
+18
View File
@@ -0,0 +1,18 @@
import ApplicationSerializer from './application';
import { underscore } from '@ember/string';
export default ApplicationSerializer.extend({
modelName: 'user',
keyForRelationshipIds(relationshipName) {
return `${this._container.inflector.singularize(
underscore(relationshipName)
)}_ids`;
},
_hashForModel(model) {
const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments);
if (model.accountIds.length) json.account_ids = model.accountIds;
return json;
}
});
+2
View File
@@ -47,11 +47,13 @@
"ember-cli-dependency-checker": "^3.2.0",
"ember-cli-htmlbars": "^5.2.0",
"ember-cli-inject-live-reload": "^2.0.2",
"ember-cli-mirage": "^1.1.8",
"ember-cli-sass": "^10.0.1",
"ember-cli-sri": "^2.1.1",
"ember-cli-template-lint": "^2.0.2",
"ember-cli-uglify": "^3.0.0",
"ember-data": "~3.20.0",
"ember-data-model-fragments": "^4.0.0",
"ember-electron": "https://github.com/adopted-ember-addons/ember-electron#v3.0.0-beta.4",
"ember-exam": "^6.0.1",
"ember-export-application-global": "^2.0.1",
+15 -2
View File
@@ -8229,7 +8229,7 @@ ember-cli-babel@7.21.0:
rimraf "^3.0.1"
semver "^5.5.0"
ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.11.0, ember-cli-babel@^6.12.0, ember-cli-babel@^6.16.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.8.2, ember-cli-babel@^6.9.2:
ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.11.0, ember-cli-babel@^6.12.0, ember-cli-babel@^6.16.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.8.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.8.2, ember-cli-babel@^6.9.2:
version "6.18.0"
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.18.0.tgz#3f6435fd275172edeff2b634ee7b29ce74318957"
integrity sha512-7ceC8joNYxY2wES16iIBlbPSxwKDBhYwC8drU3ZEvuPDMwVv1KzxCNu1fvxyFEBWhwaRNTUxSCsEVoTd9nosGA==
@@ -8396,7 +8396,7 @@ ember-cli-lodash-subset@^2.0.1:
resolved "https://registry.yarnpkg.com/ember-cli-lodash-subset/-/ember-cli-lodash-subset-2.0.1.tgz#20cb68a790fe0fde2488ddfd8efbb7df6fe766f2"
integrity sha1-IMtop5D+D94kiN39jvu332/nZvI=
ember-cli-mirage@^1.1.6, ember-cli-mirage@^1.1.7:
ember-cli-mirage@^1.1.6, ember-cli-mirage@^1.1.7, ember-cli-mirage@^1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/ember-cli-mirage/-/ember-cli-mirage-1.1.8.tgz#b3874a0f2eb83d8f341416d3a691ff82735c629c"
integrity sha512-i0/CNwdyza+o+0RW92i/dEhAqTcyoAJ9jGKKiOLK74RwDG0Bgt5w/CdqKfIFBf/lIGPXHPveneWsyckeSiVr4A==
@@ -8775,6 +8775,19 @@ ember-data-model-fragments@5.0.0-beta.0:
git-repo-info "^2.0.0"
npm-git-info "^1.0.3"
ember-data-model-fragments@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/ember-data-model-fragments/-/ember-data-model-fragments-4.0.0.tgz#8570d8b6bef12e8a3518279af01478ef8818a44f"
integrity sha512-DcRRQAWrrfRQBW8+v6takQdS2hNJ9rKWU/uHjnPY6+Vg/y6TxyPU8mo0PF2z0DYAeXwoIQ05iYTJT0sqFPEo+w==
dependencies:
broccoli-file-creator "^2.1.1"
broccoli-merge-trees "^3.0.0"
calculate-cache-key-for-tree "^1.1.0"
ember-cli-babel "^6.8.0"
ember-copy "1.0.0"
git-repo-info "^2.0.0"
npm-git-info "^1.0.3"
ember-data@^3.16.0:
version "3.22.1"
resolved "https://registry.yarnpkg.com/ember-data/-/ember-data-3.22.1.tgz#5b075fc8a2c3f18dac2305037d34b88666631b1a"