diff --git a/README.md b/README.md
index b5703017..780dd8f5 100644
--- a/README.md
+++ b/README.md
@@ -72,6 +72,36 @@ module.exports = function(environment) {
## Usage
+### Launch Darkly Service
+
+ember-launch-darkly automatically injects the launch darkly service, as `launchDarkly` in to the following Ember objects:
+
+- all `route`s
+- all `controller`s
+- all `component`s
+- `router:main`
+
+This means that it can be accessed without an explicit injection, like so:
+
+```js
+// /app/application/route.js
+
+import Route from '@ember/routing/route';
+
+export default Route.extend({
+ model() {
+ let user = {
+ key: 'aa0ceb',
+ anonymous: true
+ };
+
+ return this.launchDarkly.initialize(user);
+ }
+});
+```
+
+Due to Ember not allowing auto injection of a service in to another service, we are currently unable to auto inject `launchDarkly` in to other services. This means that if you would like to check for Launch Darkly flags in your service, you will need to explicitly inject the `launchDarkly` service yourself.
+
### Initialize
Before being used, Launch Darkly must be initialized. This should happen early so choose an appropriate place to make the call such as an application initializer or the application route.
@@ -84,11 +114,8 @@ The user `key` is the only required attribute, see the [Launch Darkly documentat
// /app/application/route.js
import Route from '@ember/routing/route';
-import { inject as service } from '@ember/service';
export default Route.extend({
- launchDarkly: service(),
-
model() {
let user = {
key: 'aa0ceb',
@@ -102,17 +129,15 @@ export default Route.extend({
### Identify
-If you initialized Launch Darkly with an anonymous user and want to re-initialize it for a specific user to receive the flags for that user, you can use the `identify`. This can only be called after `initialize` has been called.
+If you initialized Launch Darkly with an anonymous user and want to re-initialize it for a specific user to receive the flags for that user, you can use `identify`. This can only be called after `initialize` has been called.
```js
// /app/session/route.js
import Route from '@ember/routing/route';
-import { inject as service } from '@ember/service';
export default Route.extend({
session: service(),
- launchDarkly: service(),
model() {
return this.session.getSession();
@@ -132,7 +157,7 @@ export default Route.extend({
### Templates
-ember-launch-darkly provides a `variation` template helper to check your feature flags.
+ember-launch-darkly provides a `variation` helper to check your feature flags in your handlebars templates.
If your feature flag is a boolean based flag, you might use it in an `{{if}}` like so:
@@ -167,11 +192,8 @@ If your feature flag is a boolean based flag, you might use it in a function lik
import Component from '@ember/component';
import { computed } from '@ember/object';
-import { inject as service } from '@ember/service';
export default Component.extend({
- launchDarkly: service(),
-
actions: {
getPrice() {
if (this.launchDarkly.variation('new-price-plan')) {
@@ -191,11 +213,8 @@ If your feature flag is a multivariate based flag, you might use it in a functio
import Component from '@ember/component';
import { computed } from '@ember/object';
-import { inject as service } from '@ember/service';
export default Component.extend({
- launchDarkly: service(),
-
actions: {
getPrice() {
switch (this.launchDarkly.variation('new-pricing-plan')) {
@@ -220,11 +239,8 @@ And if you want to check a flag in a computed property, and have it recompute wh
import Component from '@ember/component';
import { computed } from '@ember/object';
-import { inject as service } from '@ember/service';
export default Component.extend({
- launchDarkly: service(),
-
price: computed('launchDarkly.new-price-plan', function() {
if (this.launchDarkly['new-price-plan']) {
return 99.00;
@@ -237,7 +253,7 @@ export default Component.extend({
### [EXPERIMENTAL] `variation` Javascript helper
-ember-launch-darkly provides a special, experimental, `variation` import that can be used in Javascript files such as Components, to make the invocation and checking of feature flags a little nicer in the JS code.
+ember-launch-darkly provides a special, _experimental_, `variation` import that can be used in Javascript files such as Components, to make the invocation and checking of feature flags a little nicer in the JS code.
This helper is backed by a Babel transform that essentially replaces the helper with the code above in the [Javascript](#javascript) section. The main benefits this helper provides is:
@@ -245,7 +261,7 @@ This helper is backed by a Babel transform that essentially replaces the helper
- Removes boiler plate launch darkly code (injection of service, referencing service in functions, etc)
- Provide a syntax that is parallel to the `variation` helper used in templates
-The babel transform that replaces this `variation` helper in the JS code is experimental. Your mileage may vary.
+The babel transform that replaces this `variation` helper in the JS code is experimental. YMMV.
If you would like to try it out, simply enable it in your `ember-cli-build.js` as follows:
@@ -257,7 +273,7 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app');
module.exports = function(defaults) {
let app = new EmberApp(defaults, {
babel: {
- plugins: [ require.resolve('ember-launch-darkly/launch-darkly-variation-helper') ]
+ plugins: [ require.resolve('ember-launch-darkly/babel-plugin') ]
}
});
@@ -271,10 +287,29 @@ Then import the helper from `ember-launch-darkly` and use it as follows:
// /app/components/login-page/component.js
import Component from '@ember/component';
-import { computed } from '@ember/object';
import { variation } from 'ember-launch-darkly';
+export default Component.extend({
+ price() {
+ if (variation('new-pricing-plan')) {
+ return 99.00;
+ }
+
+ return 199.00;
+ }
+});
+```
+
+If you would like the feature flag to recompute a computed property when it
+changes, you will need to also use the `computedWithVariation` import, like so:
+
+```js
+// /app/components/login-page/component.js
+
+import Component from '@ember/component';
+import { variation, computedWithVariation as computed } from 'ember-launch-darkly';
+
export default Component.extend({
price: computed(function() {
if (variation('new-pricing-plan')) {
@@ -286,20 +321,19 @@ export default Component.extend({
});
```
-For reference, the babel transform should transform the above code in to the following:
+The `computedWithvariation` import is literally a direct re-export of the `@ember/object` computed. We need to re-export it so that we can signal to the babel transform where to auto insert the feature flag dependent keys. Because `computedWithVariation` is a re-export you can alias it as `computed` (like above) and use it anywhere you would use a normal `computed`.
+
+For reference, the babel transform should transform the above code in to, _roughly_, the following:
```js
// /app/components/login-page/component.js
import Component from '@ember/component';
import { computed } from '@ember/object';
-import { inject as service } from '@ember/service';
export default Component.extend({
- ldService: service(),
-
- price: computed('ldService.new-pricing-plan', function() {
- if (this.ldService['new-pricing-plan']) {
+ price: computed('launchDarkly.new-pricing-plan', function() {
+ if (this.launchDarkly['new-pricing-plan']) {
return 99.00;
}
@@ -308,6 +342,59 @@ export default Component.extend({
});
```
+It's important to note that the `variation` helper will only work when used
+inside objects that have an `owner`. This is because the helper is transformed
+in to a call to `this.launchDarkly` which is an injected service. Therefore, the
+`variation` helper will not work in a computed returned from, say, a function.
+Like so:
+
+```js
+export default function myCoolFn() {
+ if (variation('foo')) {
+ return 'I love cheese';
+ }
+
+ return 'I love bacon';
+}
+```
+
+For the reason above, if you would like to use the variation helper in one of your own objects that
+extends `EmberObject`, you will need to create the `EmberObject` instance with
+an owner injection, like so:
+
+```js
+// /app/components/login-page/component.js
+
+import Component from '@ember/component';
+import EmberObject from '@ember/object';
+import { getOwner } from '@ember/application';
+
+const MyFoo = EmberObject.extend({
+ price() {
+ return variation('my-price');
+ }
+});
+
+export default Component.extend({
+ createFoo() {
+ let owner = getOwner(this);
+
+ return MyFoo.create(owner.ownerInjection(), {
+ someProp: 'bar'
+ });
+ }
+});
+
+```
+
+As this babel transform is experimental, it does not currently support the
+following:
+
+- Native classes
+- Decorators
+
+We will endeavour to add support for these things in the future.
+
## Local feature flags
When `local: true` is set in the Launch Darkly configuration, ember-launch-darkly will retrieve the feature flags and their values from `config/environment.js` instead of the Launch Darkly service. This is useful for development purposes so you don't need to set up a new environment in Launch Darkly, your app doesn't need to make a request for the flags, and you can easily change the value of the flags from the browser console.
@@ -432,7 +519,7 @@ module('Acceptance | Homepage', function(hooks) {
### Integration Tests
-Use the test client and `withVariation` helper in component tests to control feature flags as well.
+Use the `setupLaunchDarkly` hook and `withVariation` helper in component tests to control feature flags as well.
```js
import { module, test } from 'qunit';
diff --git a/addon/index.js b/addon/index.js
new file mode 100644
index 00000000..c5f20886
--- /dev/null
+++ b/addon/index.js
@@ -0,0 +1,19 @@
+import { DEBUG } from '@glimmer/env';
+
+let variation;
+if (DEBUG) {
+ variation = () => {
+ throw new Error(
+ `
+
+In order to use the "variation" Javascript helper, you must include the ember-launch-darkly babel plugin. See the README (link below) for more details on how this works and some considerations when using it.
+
+https://github.com/ember-launch-darkly/ember-launch-darkly#experimental-variation-javascript-helper
+`
+ );
+ };
+}
+
+export { variation };
+
+export { computed as computedWithVariation } from '@ember/object';
diff --git a/addon/initializers/launch-darkly.js b/addon/initializers/launch-darkly.js
index 86ae82a0..8a625c9e 100644
--- a/addon/initializers/launch-darkly.js
+++ b/addon/initializers/launch-darkly.js
@@ -14,6 +14,10 @@ export function initialize(application) {
let Factory = config.local ? LocalClient : RemoteClient;
application.register('service:launch-darkly-client', Factory);
+
+ ['route', 'controller', 'component', 'router:main'].forEach(type => {
+ application.inject(type, 'launchDarkly', 'service:launch-darkly');
+ });
}
export default {
diff --git a/babel-plugin/index.js b/babel-plugin/index.js
new file mode 100644
index 00000000..353264ca
--- /dev/null
+++ b/babel-plugin/index.js
@@ -0,0 +1,106 @@
+const SERVICE_PROPERTY_NAME = 'launchDarkly';
+
+module.exports = function launchDarklyBabelPlugin(babel) {
+ const { types: t } = babel;
+
+ const importVisitor = {
+ ImportDeclaration(path) {
+ if (path.get('source').node.value === 'ember-launch-darkly') {
+ let obj = path
+ .get('specifiers')
+ .find(
+ obj =>
+ obj.isImportSpecifier() &&
+ obj.get('imported').node.name === 'variation'
+ );
+
+ if (obj) {
+ if (path.get('specifiers').length === 1) {
+ path.remove();
+ } else {
+ obj.remove();
+ }
+ }
+ path.stop();
+ }
+ }
+ };
+
+ return {
+ name: 'ember-launch-darkly',
+ visitor: {
+ Program: {
+ exit(path) {
+ path.traverse(importVisitor);
+ }
+ },
+
+ CallExpression(path) {
+ if (_isReferenceToComputedHelper(path)) {
+ _handleComputedHelper(path, t);
+ }
+
+ if (_isReferenceToVariationHelper(path)) {
+ _handleVariationHelper(path, t);
+ }
+ }
+ }
+ };
+};
+
+module.exports.baseDir = function() {
+ return __dirname;
+};
+
+function _handleComputedHelper(path, t) {
+ let state = { flags: [] };
+ path.traverse(
+ {
+ CallExpression(path) {
+ if (_isReferenceToVariationHelper(path)) {
+ let flagName = path.node.arguments[0] && path.node.arguments[0].value;
+
+ if (flagName) {
+ this.flags.push(flagName);
+ }
+ }
+ }
+ },
+ state
+ );
+
+ if (state.flags.length) {
+ let dependentKey;
+
+ if (state.flags.length === 1) {
+ dependentKey = `${SERVICE_PROPERTY_NAME}.${state.flags[0]}`;
+ } else {
+ dependentKey = `${SERVICE_PROPERTY_NAME}.{${state.flags.join(',')}}`;
+ }
+
+ path.node.arguments.unshift(t.stringLiteral(dependentKey));
+ }
+}
+
+function _handleVariationHelper(path, t) {
+ let flagName = path.node.arguments[0] && path.node.arguments[0].value;
+
+ let ldServiceCallExpression = t.callExpression(
+ t.memberExpression(t.thisExpression(), t.identifier('get')),
+ [t.stringLiteral(`${SERVICE_PROPERTY_NAME}.${flagName}`)]
+ );
+
+ path.replaceWith(ldServiceCallExpression);
+}
+
+function _isReferenceToComputedHelper(path) {
+ return path
+ .get('callee')
+ .referencesImport('ember-launch-darkly', 'computedWithVariation');
+}
+
+function _isReferenceToVariationHelper(path) {
+ return path
+ .get('callee')
+ .referencesImport('ember-launch-darkly', 'variation');
+}
diff --git a/ember-cli-build.js b/ember-cli-build.js
index 6c305dc1..86570c76 100644
--- a/ember-cli-build.js
+++ b/ember-cli-build.js
@@ -5,7 +5,7 @@ const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
module.exports = function(defaults) {
let app = new EmberAddon(defaults, {
babel: {
- plugins: [ require.resolve('./launch-darkly-variation-helper') ]
+ plugins: [ require.resolve('./babel-plugin') ]
},
snippetSearchPaths: ['app', 'tests/dummy'],
diff --git a/launch-darkly-variation-helper.js b/launch-darkly-variation-helper.js
deleted file mode 100644
index 7138d98c..00000000
--- a/launch-darkly-variation-helper.js
+++ /dev/null
@@ -1,160 +0,0 @@
-/* eslint-disable */
-
-const SERVICE_PROPERTY_NAME = 'ldService';
-
-module.exports = function launchDarklyVariationHelperPlugin({ types: t }) {
- return {
- name: 'launch-darkly-variation-helper',
- visitor: {
- Program: {
- enter(path, state) {
- let variationHelperImport = _findVariationHelperImport(path, t);
-
- if (variationHelperImport && _isReferenced(variationHelperImport, t)) {
- state.shouldInjectLaunchDarklyService = true;
- }
- },
-
- exit(path) {
- let variationHelperImport = _findVariationHelperImport(path, t);
-
- if (variationHelperImport) {
- _removeVariationHelperImport(variationHelperImport, t);
- }
- }
- },
-
- Identifier(path) {
- if (_isReferenceToVariationHelper(path)) {
- let variationHelperCallExpression = path.findParent(p => t.isCallExpression(p));
-
- if (_isVariationHelperInsideComputedProperty(variationHelperCallExpression, t)) {
- _insertFeatureFlagAsComputedPropertyDependentKey(variationHelperCallExpression, t);
- }
-
- _replaceVariationHelperCallWithLaunchDarklyServiceCall(variationHelperCallExpression, t);
- }
- },
-
- CallExpression(path, state) {
- if (state.shouldInjectLaunchDarklyService) {
- _insertLaunchDarklyServiceInjectionIntoTopLevelObject(path, t);
- state.shouldInjectLaunchDarklyService = false;
- }
- }
- }
- };
-}
-
-module.exports.baseDir = function() { return __dirname };
-
-function _isReferenceToVariationHelper(path) {
- return path.referencesImport('ember-launch-darkly', 'variation');
-}
-
-function _replaceVariationHelperCallWithLaunchDarklyServiceCall(path, t) {
- let featureFlagKey = path.get('arguments.0').node.value;
-
- let launchDarklyServiceCallExpression = t.callExpression(
- t.memberExpression(t.thisExpression(),
- t.identifier('get')),
- [t.stringLiteral(`${SERVICE_PROPERTY_NAME}.${featureFlagKey}`)]
- );
-
- path.replaceWith(launchDarklyServiceCallExpression);
-}
-
-function _isVariationHelperInsideComputedProperty(path, t) {
- return !!_findParentComputedProperty(path, t);
-}
-
-function _insertFeatureFlagAsComputedPropertyDependentKey(path, t) {
- let featureFlagKey = path.get('arguments.0').node.value;
- let dependentKey = `${SERVICE_PROPERTY_NAME}.${featureFlagKey}`;
- let parent = _findParentComputedProperty(path, t);
-
- parent.node.arguments.unshift(t.stringLiteral(dependentKey));
-}
-
-function _findParentComputedProperty(path, t) {
- let parentComputed = path.findParent(p => {
- let isComputed = false;
-
- if (t.isCallExpression(p)) {
- let callee = p.get('callee');
-
- if (t.isMemberExpression(callee)) {
- if (callee.node.object.name === 'Ember' && callee.node.property.name === 'computed') {
- isComputed = true;
- }
- }
- }
-
- return isComputed;
- });
-
- return parentComputed;
-}
-
-function _findVariationHelperImport(path, t) {
- return path.get('body')
- .filter(obj => t.isImportDeclaration(obj))
- .find(obj => _isVariationImport(obj, t));
-}
-
-function _importSpecifier(path, t) {
- return path.get('specifiers')
- .find(obj => t.isImportSpecifier(obj) && obj.get('imported').node.name === 'variation');
-}
-
-function _isVariationImport(path, t) {
- if (path.get('source').node.value === 'ember-launch-darkly') {
- let specifier = _importSpecifier(path, t);
-
- return !!specifier;
- }
-}
-
-function _isReferenced(path, t) {
- let specifier = _importSpecifier(path, t);
- let localName = specifier.get('local').node.name;
- return specifier.scope.bindings[localName].references > 0;
-}
-
-function _isOnlyImportFromModule(path) {
- return path.get('specifiers').length === 1;
-}
-
-function _removeVariationHelperImport(path, t) {
- if (_isOnlyImportFromModule(path)) {
- path.remove();
- } else {
- _importSpecifier(path, t).remove();
- }
-}
-
-function _insertLaunchDarklyServiceInjectionIntoTopLevelObject(path, t) {
- let callee = path.get('callee');
-
- if (t.isMemberExpression(callee)) {
- let property = callee.get('property');
-
- if (t.isIdentifier(property) && property.node.name === 'extend') {
- let object = path.get('arguments').find(arg => t.isObjectExpression(arg));
-
- if (object) {
- object.unshiftContainer('properties', _buildServiceInjection(t));
- }
- }
- }
-}
-
-function _buildServiceInjection(t) {
- return t.objectProperty(
- t.identifier(SERVICE_PROPERTY_NAME),
- t.callExpression(
- t.identifier('Ember.inject.service'),
- [t.stringLiteral('launchDarkly')]
- )
- );
-}
diff --git a/nodetests/__fixtures__/helper-invocations/expected.js b/nodetests/__fixtures__/helper-invocations/expected.js
deleted file mode 100644
index 39fa3edb..00000000
--- a/nodetests/__fixtures__/helper-invocations/expected.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import Component from '@ember/component';
-import { computed } from '@ember/object';
-import EmberObject from '@ember/object';
-import { inject as service } from '@ember/service';
-import { task } from 'ember-concurrency';
-export default Component.extend({
- ldService: Ember.inject.service("launchDarkly"),
- launchDarkly: service(),
- price: Ember.computed("ldService.apply-discount", 'price', function () {
- if (this.get("ldService.apply-discount")) {
- return this.get('price') * 0.5;
- }
-
- return this.get('price');
- }),
- foo: Ember.computed("ldService.apply-discount", function () {
- if (this.get("ldService.apply-discount")) {
- return this.get('price') * 0.5;
- }
-
- return this.get('price');
- }),
- bar: Ember.computed("ldService.apply-discount", 'launchDarkly.apply-discount', function () {
- if (this.get("ldService.apply-discount")) {
- return this.get('price') * 0.5;
- }
-
- return this.get('price');
- }),
- baz: Ember.computed("ldService.baz", "ldService.bar", function () {
- if (this.get("ldService.bar") || this.get("ldService.baz")) {
- return null;
- }
- }),
- bop: Ember.computed("ldService.bar", function () {
- return EmberObject.create({
- bar() {
- if (this.get("ldService.bar")) {
- return null;
- }
- }
-
- });
- }),
-
- otherPrice() {
- if (this.get("ldService.apply-discount")) {
- return this.get('price') * 0.5;
- }
-
- return this.get('price');
- },
-
- yar: function () {
- if (this.get("ldService.apply-discount")) {
- return this.get('price') * 0.5;
- }
-
- return this.get('price');
- },
- mar: task(function* () {
- if (this.get("ldService.apply-discount")) {
- return this.get('price') * 0.5;
- }
-
- return yield this.get('price');
- })
-});
diff --git a/nodetests/__fixtures__/helper-invocations/input.js b/nodetests/__fixtures__/helper-invocations/input.js
deleted file mode 100644
index 200c2865..00000000
--- a/nodetests/__fixtures__/helper-invocations/input.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import Component from '@ember/component';
-import {computed } from '@ember/object';
-import EmberObject from '@ember/object';
-import { inject as service } from '@ember/service';
-
-import { variation } from 'ember-launch-darkly';
-
-import { task } from 'ember-concurrency';
-
-export default Component.extend({
- launchDarkly: service(),
-
- price: Ember.computed('price', function () {
- if (variation('apply-discount')) {
- return this.get('price') * 0.5;
- }
-
- return this.get('price');
- }),
-
- foo: Ember.computed(function () {
- if (variation('apply-discount')) {
- return this.get('price') * 0.5;
- }
-
- return this.get('price');
- }),
-
- bar: Ember.computed('launchDarkly.apply-discount', function () {
- if (variation('apply-discount')) {
- return this.get('price') * 0.5;
- }
-
- return this.get('price');
- }),
-
- baz: Ember.computed(function() {
- if(variation('bar') || variation('baz')) {
- return null;
- }
- }),
-
- bop: Ember.computed(function() {
- return EmberObject.create({
- bar() {
- if(variation('bar')) {
- return null;
- }
- }
- });
- }),
-
- otherPrice() {
- if (variation('apply-discount')) {
- return this.get('price') * 0.5;
- }
-
- return this.get('price');
- },
-
- yar: function() {
- if (variation('apply-discount')) {
- return this.get('price') * 0.5;
- }
-
- return this.get('price');
- },
-
- mar: task(function* () {
- if (variation('apply-discount')) {
- return this.get('price') * 0.5;
- }
-
- return yield this.get('price');
- })
-});
diff --git a/nodetests/babel-plugin-test.js b/nodetests/babel-plugin-test.js
new file mode 100644
index 00000000..3ed84206
--- /dev/null
+++ b/nodetests/babel-plugin-test.js
@@ -0,0 +1,212 @@
+const pluginTester = require('babel-plugin-tester');
+const plugin = require('../babel-plugin');
+
+pluginTester({
+ plugin,
+ title: 'Launch Darkly Babel Plugin',
+ snapshot: false,
+ tests: [
+ {
+ title: 'Variation helper inside Ember computed',
+ code: `
+ import { Component } from '@ember/component';
+ import { computed } from '@ember/computed';
+ import { variation } from 'ember-launch-darkly';
+
+ export default Component.extend({
+ discount: computed(function () {
+ if (variation('new-pricing')) {
+ return 5;
+ }
+
+ return 2;
+ })
+ });
+ `,
+ output: `
+ import { Component } from '@ember/component';
+ import { computed } from '@ember/computed';
+ export default Component.extend({
+ discount: computed(function () {
+ if (this.get("launchDarkly.new-pricing")) {
+ return 5;
+ }
+
+ return 2;
+ })
+ });
+ `
+ },
+
+ {
+ title: 'Variation helper with local name inside Ember computed',
+ code: `
+ import { Component } from '@ember/component';
+ import { computed } from '@ember/computed';
+ import { variation as vvv } from 'ember-launch-darkly';
+
+ export default Component.extend({
+ discount: computed(function () {
+ if (vvv('new-pricing')) {
+ return 5;
+ }
+
+ return 2;
+ })
+ });
+ `,
+ output: `
+ import { Component } from '@ember/component';
+ import { computed } from '@ember/computed';
+ export default Component.extend({
+ discount: computed(function () {
+ if (this.get("launchDarkly.new-pricing")) {
+ return 5;
+ }
+
+ return 2;
+ })
+ });
+ `
+ },
+
+ {
+ title: 'Variation helper inside computedWithVariation',
+ code: `
+ import { Component } from '@ember/component';
+ import { variation, computedWithVariation } from 'ember-launch-darkly';
+
+ export default Component.extend({
+ discount: computedWithVariation(function () {
+ if (variation('new-pricing')) {
+ return 5;
+ }
+
+ return 2;
+ }),
+
+ colors: computedWithVariation(function() {
+ if (variation('red')) {
+ return 'red';
+ }
+
+ if (variation('green')) {
+ return 'green';
+ }
+
+ return 'blue';
+ })
+ });
+ `,
+ output: `
+ import { Component } from '@ember/component';
+ import { computedWithVariation } from 'ember-launch-darkly';
+ export default Component.extend({
+ discount: computedWithVariation("launchDarkly.new-pricing", function () {
+ if (this.get("launchDarkly.new-pricing")) {
+ return 5;
+ }
+
+ return 2;
+ }),
+ colors: computedWithVariation("launchDarkly.{red,green}", function () {
+ if (this.get("launchDarkly.red")) {
+ return 'red';
+ }
+
+ if (this.get("launchDarkly.green")) {
+ return 'green';
+ }
+
+ return 'blue';
+ })
+ });
+ `
+ },
+
+ {
+ title: 'Variation helper inside computedWithVariation with local name',
+ code: `
+ import { Component } from '@ember/component';
+ import { variation, computedWithVariation as computed } from 'ember-launch-darkly';
+
+ export default Component.extend({
+ discount: computed(function () {
+ if (variation('new-pricing')) {
+ return 5;
+ }
+
+ return 2;
+ }),
+
+ colors: computed(function() {
+ if (variation('red')) {
+ return 'red';
+ }
+
+ if (variation('green')) {
+ return 'green';
+ }
+
+ return 'blue';
+ })
+ });
+ `,
+ output: `
+ import { Component } from '@ember/component';
+ import { computedWithVariation as computed } from 'ember-launch-darkly';
+ export default Component.extend({
+ discount: computed("launchDarkly.new-pricing", function () {
+ if (this.get("launchDarkly.new-pricing")) {
+ return 5;
+ }
+
+ return 2;
+ }),
+ colors: computed("launchDarkly.{red,green}", function () {
+ if (this.get("launchDarkly.red")) {
+ return 'red';
+ }
+
+ if (this.get("launchDarkly.green")) {
+ return 'green';
+ }
+
+ return 'blue';
+ })
+ });
+ `
+ },
+
+ {
+ title: 'Variation helper inside standard function',
+ code: `
+ import { Component } from '@ember/component';
+ import { variation } from 'ember-launch-darkly';
+
+ export default Component.extend({
+ discount() {
+ if (variation('new-pricing')) {
+ return 5;
+ }
+
+ return 2;
+ }
+ });
+ `,
+ output: `
+ import { Component } from '@ember/component';
+ export default Component.extend({
+ discount() {
+ if (this.get("launchDarkly.new-pricing")) {
+ return 5;
+ }
+
+ return 2;
+ }
+
+ });
+ `
+ }
+ ]
+});
diff --git a/nodetests/launch-darkly-variation-helper-plugin-test.js b/nodetests/launch-darkly-variation-helper-plugin-test.js
deleted file mode 100644
index 1ba82ccf..00000000
--- a/nodetests/launch-darkly-variation-helper-plugin-test.js
+++ /dev/null
@@ -1,228 +0,0 @@
-const pluginTester = require('babel-plugin-tester');
-const plugin = require('../launch-darkly-variation-helper');
-
-pluginTester({
- plugin,
- title: 'Import transformations',
- snapshot: false,
- tests: [
- {
- title: 'Single import',
- code: `
- import foo from 'foo';
- import { variation } from 'ember-launch-darkly';
- `,
- output: `
- import foo from 'foo';
- `
- },
- {
- title: 'Multiple imports',
- code: `
- import foo from 'foo';
- import { bar, variation, baz } from 'ember-launch-darkly';
- `,
- output: `
- import foo from 'foo';
- import { bar, baz } from 'ember-launch-darkly';
- `
- },
- {
- title: 'Single import (aliased)',
- code: `
- import foo from 'foo';
- import { variation as v } from 'ember-launch-darkly';
- `,
- output: `import foo from 'foo';`
- },
- {
- title: 'Multiple imports (aliased)',
- code: `
- import foo from 'foo';
- import { bar, variation as v, baz } from 'ember-launch-darkly';
- `,
- output: `
- import foo from 'foo';
- import { bar, baz } from 'ember-launch-darkly';
- `
- }
- ]
-});
-
-pluginTester({
- plugin,
- title: 'Launch Darkly service injection',
- snapshot: false,
- tests: [
- {
- title: 'Export inline object as default',
- code: `
- import { variation } from 'ember-launch-darkly';
-
- export default Component.extend({
- foo: Ember.computed(function() {
- if(variation('bar')) {
- return null;
- }
- })
- });
- `,
- output: `
- export default Component.extend({
- ldService: Ember.inject.service("launchDarkly"),
- foo: Ember.computed("ldService.bar", function () {
- if (this.get("ldService.bar")) {
- return null;
- }
- })
- });
- `
- },
- {
- title: 'Export variable declaration as default',
- code: `
- import { variation } from 'ember-launch-darkly';
-
- const Thing = Component.extend({
- foo: Ember.computed(function() {
- if(variation('bar')) {
- return null;
- }
- })
- });
-
- export default Thing;
- `,
- output: `
- const Thing = Component.extend({
- ldService: Ember.inject.service("launchDarkly"),
- foo: Ember.computed("ldService.bar", function () {
- if (this.get("ldService.bar")) {
- return null;
- }
- })
- });
- export default Thing;
- `
- },
- {
- title: 'Export variable declaration as member',
- code: `
- import { variation } from 'ember-launch-darkly';
-
- const Thing = Component.extend({
- foo: Ember.computed(function() {
- if(variation('bar')) {
- return null;
- }
- })
- });
-
- export { Thing };
- `,
- output: `
- const Thing = Component.extend({
- ldService: Ember.inject.service("launchDarkly"),
- foo: Ember.computed("ldService.bar", function () {
- if (this.get("ldService.bar")) {
- return null;
- }
- })
- });
- export { Thing };
- `
- },
- {
- title: 'Include mixins in object',
- code: `
- import { variation } from 'ember-launch-darkly';
- import SomeMixin from 'somewhere';
-
- export default Component.extend(SomeMixin, {
- foo: Ember.computed(function() {
- if(variation('bar')) {
- return null;
- }
- })
- });
- `,
- output: `
- import SomeMixin from 'somewhere';
- export default Component.extend(SomeMixin, {
- ldService: Ember.inject.service("launchDarkly"),
- foo: Ember.computed("ldService.bar", function () {
- if (this.get("ldService.bar")) {
- return null;
- }
- })
- });
- `
- }
- ]
-});
-
-pluginTester({
- plugin,
- title: 'Variation invocation transformations',
- snapshot: false,
- filename: __filename,
- tests: [
- {
- title: 'Base helper invocations',
- fixture: '__fixtures__/helper-invocations/input.js',
- outputFixture: '__fixtures__/helper-invocations/expected.js'
- },
- {
- title: 'Invoke using alias',
- code: `
- import Ember from 'ember';
- import { variation as foo } from 'ember-launch-darkly';
-
- export default Component.extend({
- foo: Ember.computed(function() {
- if(foo('bar')) {
- return null;
- }
- })
- });
- `,
- output: `
- import Ember from 'ember';
- export default Component.extend({
- ldService: Ember.inject.service("launchDarkly"),
- foo: Ember.computed("ldService.bar", function () {
- if (this.get("ldService.bar")) {
- return null;
- }
- })
- });
- `
- }
- ]
-});
-
-pluginTester({
- plugin,
- title: 'Code that should not be transformed',
- snapshot: false,
- tests: [
- {
- title: 'An Object creation that does not include a reference to the variation helper',
- code: `
- import Ember from 'ember';
- import Resolver from './resolver';
- import loadInitializers from 'ember-load-initializers';
- import config from './config/environment';
- let App;
- Ember.MODEL_FACTORY_INJECTIONS = true;
- App = Ember.Application.extend({
- modulePrefix: config.modulePrefix,
- podModulePrefix: config.podModulePrefix,
- Resolver
- });
- loadInitializers(App, config.modulePrefix);
- export default App;
- `
- }
- ]
-});
diff --git a/package.json b/package.json
index eca7ba12..13512987 100644
--- a/package.json
+++ b/package.json
@@ -54,6 +54,7 @@
"ember-load-initializers": "^2.0.0",
"ember-maybe-import-regenerator": "^0.1.6",
"ember-native-dom-helpers": "^0.6.3",
+ "ember-prism": "^0.5.0",
"ember-qunit": "^4.4.1",
"ember-resolver": "^5.0.1",
"ember-source": "~3.11.1",
diff --git a/tests/acceptance/variations-test.js b/tests/acceptance/variations-test.js
index 61b08389..f78ae268 100644
--- a/tests/acceptance/variations-test.js
+++ b/tests/acceptance/variations-test.js
@@ -9,7 +9,7 @@ module('Acceptance | variations', function(hooks) {
setupLaunchDarkly(hooks);
test('Defining and using feature flags', async function(assert) {
- assert.expect(10);
+ assert.expect(12);
await visit('/acceptance-tests');
@@ -26,7 +26,7 @@ module('Acceptance | variations', function(hooks) {
assert.dom('.template-if-statement').hasText('BAR', 'Test variation helper in template "if" statement');
assert.dom('.template-with-statement').hasText('BAR - true', 'Test variation helper in template "with" statement');
assert.dom('.template-let-statement').hasText('BAR', 'Test variation helper in template "let" statement');
- //assert.dom('.single-variation-computed').hasText('BAR', 'Test single computed property in controller');
- //assert.dom('.multiple-variation-computed').hasText('BAR', 'Test multiple variations in computed property in controller');
+ assert.dom('.single-variation-computed').hasText('BAR', 'Test single computed property in controller');
+ assert.dom('.multiple-variation-computed').hasText('BAR', 'Test multiple variations in computed property in controller');
});
});
diff --git a/tests/dummy/app/acceptance-tests/controller.js b/tests/dummy/app/acceptance-tests/controller.js
index afc8c6f8..a5c18698 100644
--- a/tests/dummy/app/acceptance-tests/controller.js
+++ b/tests/dummy/app/acceptance-tests/controller.js
@@ -1,8 +1,6 @@
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
-import { computed } from '@ember/object';
-
-import { variation } from 'ember-launch-darkly';
+import { variation, computedWithVariation as computed } from 'ember-launch-darkly';
export default Controller.extend({
launchDarkly: service(),
diff --git a/tests/dummy/app/login/controller.js b/tests/dummy/app/login/controller.js
index 4ccf7d6b..1864d0d0 100644
--- a/tests/dummy/app/login/controller.js
+++ b/tests/dummy/app/login/controller.js
@@ -1,6 +1,6 @@
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
-import { computed } from '@ember/object';
+import { computedWithVariation as computed } from 'ember-launch-darkly';
import { variation } from 'ember-launch-darkly';
diff --git a/tests/dummy/app/login/template.hbs b/tests/dummy/app/login/template.hbs
index 996a44c2..cf1a3675 100644
--- a/tests/dummy/app/login/template.hbs
+++ b/tests/dummy/app/login/template.hbs
@@ -3,41 +3,61 @@
-{{code-snippet name="as-cp-dependent-key.js"}}
+{{#let (get-code-snippet "as-cp-dependent-key.js") as |snippet|}}
+
+ {{snippet.source}}
+
+{{/let}}
PRICE: £ {{price}}
-{{code-snippet name="as-cp-alias.js"}}
+{{#let (get-code-snippet "as-cp-alias.js") as |snippet|}}
+
+ {{snippet.source}}
+
+{{/let}}
PRICE: £ {{yetAnotherPrice}}
-{{code-snippet name="as-variation-call.js"}}
+{{#let (get-code-snippet "as-variation-call.js") as |snippet|}}
+
+ {{snippet.source}}
+
+{{/let}}
PRICE: £ {{anotherPrice}}
-{{code-snippet name="as-if-template-helper.hbs"}}
+{{#let (get-code-snippet "as-if-template-helper.hbs") as |snippet|}}
+
+ {{snippet.source}}
+
+{{/let}}
-{{!--BEGIN-SNIPPET as-if-template-helper--}}
+{{!-- BEGIN-SNIPPET as-if-template-helper --}}
{{#if (variation "apply-discount")}}
PRICE: £ 99
{{else}}
PRICE: £ 199
{{/if}}
-{{!--END-SNIPPET--}}
+{{!-- END-SNIPPET --}}
-{{code-snippet name="as-with-template-helper.hbs"}}
+{{#let (get-code-snippet "as-with-template-helper.hbs") as |snippet|}}
+
+ {{snippet.source}}
+
+{{/let}}
{{!--BEGIN-SNIPPET as-with-template-helper--}}
{{#with (variation "apply-discount") as |v|}}
@@ -50,6 +70,10 @@ PRICE: £ {{anotherPrice}}
-{{code-snippet name="as-js-variation-helper.js"}}
+{{#let (get-code-snippet "as-js-variation-helper.js") as |snippet|}}
+
+ {{snippet.source}}
+
+{{/let}}
PRICE: £ {{foo}}
diff --git a/yarn.lock b/yarn.lock
index c2e0c185..4e1cfd0b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2544,7 +2544,7 @@ broccoli-lint-eslint@^5.0.0:
lodash.defaultsdeep "^4.6.0"
md5-hex "^2.0.0"
-broccoli-merge-trees@^1.0.0:
+broccoli-merge-trees@^1.0.0, broccoli-merge-trees@^1.1.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/broccoli-merge-trees/-/broccoli-merge-trees-1.2.4.tgz#a001519bb5067f06589d91afa2942445a2d0fdb5"
integrity sha1-oAFRm7UGfwZYnZGvopQkRaLQ/bU=
@@ -3124,6 +3124,15 @@ cli-width@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
integrity sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=
+clipboard@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d"
+ integrity sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==
+ dependencies:
+ good-listener "^1.2.2"
+ select "^1.1.2"
+ tiny-emitter "^2.0.0"
+
cliui@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
@@ -3637,6 +3646,11 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
+delegate@^3.1.2:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
+ integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==
+
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@@ -3900,7 +3914,7 @@ ember-cli-htmlbars-inline-precompile@^2.1.0:
heimdalljs-logger "^0.1.9"
silent-error "^1.1.0"
-ember-cli-htmlbars@^3.0.1:
+ember-cli-htmlbars@^3.0.0, ember-cli-htmlbars@^3.0.1:
version "3.1.0"
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-3.1.0.tgz#87806c2a0bca2ab52d4fb8af8e2215c1ca718a99"
integrity sha512-cgvRJM73IT0aePUG7oQ/afB7vSRBV3N0wu9BrWhHX2zkR7A7cUBI7KC9VPk6tbctCXoM7BRGsCC4aIjF7yrfXA==
@@ -3928,6 +3942,18 @@ 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-node-assets@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/ember-cli-node-assets/-/ember-cli-node-assets-0.2.2.tgz#d2d55626e7cc6619f882d7fe55751f9266022708"
+ integrity sha1-0tVWJufMZhn4gtf+VXUfkmYCJwg=
+ dependencies:
+ broccoli-funnel "^1.0.1"
+ broccoli-merge-trees "^1.1.1"
+ broccoli-source "^1.1.0"
+ debug "^2.2.0"
+ lodash "^4.5.1"
+ resolve "^1.1.7"
+
ember-cli-normalize-entity-name@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ember-cli-normalize-entity-name/-/ember-cli-normalize-entity-name-1.0.0.tgz#0b14f7bcbc599aa117b5fddc81e4fd03c4bad5b7"
@@ -4142,6 +4168,15 @@ ember-export-application-global@^2.0.0:
dependencies:
ember-cli-babel "^6.0.0-beta.7"
+ember-in-element-polyfill@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/ember-in-element-polyfill/-/ember-in-element-polyfill-0.2.0.tgz#3722b6488bd1609d7b501c3f208eff62ac3dbbef"
+ integrity sha512-cl7hWFhBcwbPbXOHjvupufwgQGRWnPNMXcJT9M/e07AbHVjbI7EnMqOfW1V2lJKsm6KQ1hT3frDcNQWZtS+6lQ==
+ dependencies:
+ debug "^3.1.0"
+ ember-cli-babel "^7.1.2"
+ ember-cli-version-checker "^2.1.0"
+
ember-load-initializers@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ember-load-initializers/-/ember-load-initializers-2.0.0.tgz#d4b3108dd14edb0f9dc3735553cc96dadd8a80cb"
@@ -4167,6 +4202,17 @@ ember-native-dom-helpers@^0.6.3:
broccoli-funnel "^1.1.0"
ember-cli-babel "^6.6.0"
+ember-prism@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/ember-prism/-/ember-prism-0.5.0.tgz#d8d53f345aaa6890d8541456ba05121e5626d5d1"
+ integrity sha512-IvjrS5SA08lDAvjvivV2DiQQgDu/P2f69z2HYH2vzduQw4m7BYF/+xK57zcMQWresIckH/tgKKdnJLZHLAE0Xg==
+ dependencies:
+ ember-cli-babel "^7.1.2"
+ ember-cli-htmlbars "^3.0.0"
+ ember-cli-node-assets "^0.2.2"
+ ember-in-element-polyfill "^0.2.0"
+ prismjs "^1.15.0"
+
ember-qunit@^4.4.1:
version "4.5.1"
resolved "https://registry.yarnpkg.com/ember-qunit/-/ember-qunit-4.5.1.tgz#dc4b0a794fbeb6702a02f28bf19091de0f90fd5a"
@@ -5525,6 +5571,13 @@ globby@^9.0.0:
pify "^4.0.1"
slash "^2.0.0"
+good-listener@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
+ integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=
+ dependencies:
+ delegate "^3.1.2"
+
got@^6.7.1:
version "6.7.1"
resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
@@ -7171,7 +7224,7 @@ lodash.without@~3.2.1:
lodash._basedifference "^3.0.0"
lodash.restparam "^3.0.0"
-lodash@^4.14.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.6.1:
+lodash@^4.14.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
@@ -8403,6 +8456,13 @@ printf@^0.5.1:
resolved "https://registry.yarnpkg.com/printf/-/printf-0.5.2.tgz#8546e01a1f647b1dff510ae92bdc92beb8c9b2f9"
integrity sha512-Hn0UuWqTRd94HiCJoiCNGZTnSyXJdIF3t4/4I293hezIzyH4pQ3ai4TlH/SmRCiMvR5aNMxSYWshjQWWW6J8MQ==
+prismjs@^1.15.0:
+ version "1.17.1"
+ resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.17.1.tgz#e669fcbd4cdd873c35102881c33b14d0d68519be"
+ integrity sha512-PrEDJAFdUGbOP6xK/UsfkC5ghJsPJviKgnQOoxaDbBjwc8op68Quupwt1DeAFoG8GImPhiKXAvvsH7wDSLsu1Q==
+ optionalDependencies:
+ clipboard "^2.0.0"
+
private@^0.1.6, private@^0.1.7, private@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
@@ -9025,7 +9085,7 @@ resolve@1.9.0:
dependencies:
path-parse "^1.0.6"
-resolve@^1.1.3, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1:
+resolve@^1.1.3, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1:
version "1.12.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==
@@ -9180,6 +9240,11 @@ sane@^4.0.0, sane@^4.1.0:
minimist "^1.1.1"
walker "~1.0.5"
+select@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
+ integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=
+
"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", "semver@4 || 5", "semver@^2.3.0 || 3.x || 4 || 5", semver@^5.1.0:
version "5.4.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
@@ -9926,6 +9991,11 @@ timed-out@^4.0.0, timed-out@^4.0.1:
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
+tiny-emitter@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
+ integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
+
tiny-lr@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab"