Browse Source

Merge branch 'master' into minor

Michael Bromley 3 years ago
parent
commit
65addd2eba

+ 50 - 0
packages/core/e2e/custom-field-relations.e2e-spec.ts

@@ -32,7 +32,12 @@ import { initialData } from '../../../e2e-common/e2e-initial-data';
 import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
 
 import { testSuccessfulPaymentMethod } from './fixtures/test-payment-methods';
+import {
+    UpdateProductVariantsMutation,
+    UpdateProductVariantsMutationVariables,
+} from './graphql/generated-e2e-admin-types';
 import { AddItemToOrder } from './graphql/generated-e2e-shop-types';
+import { UPDATE_PRODUCT_VARIANTS } from './graphql/shared-definitions';
 import { ADD_ITEM_TO_ORDER } from './graphql/shop-definitions';
 import { sortById } from './utils/test-order-utils';
 
@@ -123,6 +128,14 @@ customFieldConfig.User?.push({
     internal: false,
     public: true,
 });
+customFieldConfig.ProductVariant?.push({
+    name: 'cfRelatedProducts',
+    type: 'relation',
+    entity: Product,
+    list: true,
+    internal: false,
+    public: true,
+});
 
 const testResolverSpy = jest.fn();
 
@@ -856,6 +869,43 @@ describe('Custom field relations', () => {
                 expect(customer).toBeDefined();
             });
 
+            // https://github.com/vendure-ecommerce/vendure/issues/1664
+            it('successfully gets product.variants with nested custom field relation', async () => {
+                await adminClient.query(gql`
+                    mutation {
+                        updateProductVariants(
+                            input: [{ id: "T_1", customFields: { cfRelatedProductsIds: ["T_2"] } }]
+                        ) {
+                            id
+                        }
+                    }
+                `);
+
+                const { product } = await adminClient.query(gql`
+                    query {
+                        product(id: "T_1") {
+                            variants {
+                                id
+                                customFields {
+                                    cfRelatedProducts {
+                                        featuredAsset {
+                                            id
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                `);
+
+                expect(product).toBeDefined();
+                expect(product.variants[0].customFields.cfRelatedProducts).toEqual([
+                    {
+                        featuredAsset: { id: 'T_2' },
+                    },
+                ]);
+            });
+
             it('successfully gets product by slug with eager-loading custom field relation', async () => {
                 const { product } = await shopClient.query(gql`
                     query {

+ 39 - 0
packages/core/e2e/order-promotion.e2e-spec.ts

@@ -708,6 +708,45 @@ describe('Promotions applied to Orders', () => {
                 expect(applyCouponCode!.discounts[0].description).toBe('20% discount on order');
                 expect(applyCouponCode!.totalWithTax).toBe(4800);
             });
+
+            // https://github.com/vendure-ecommerce/vendure/issues/1773
+            it('decimal percentage', async () => {
+                const decimalPercentageCouponCode = 'DPCC';
+                await createPromotion({
+                    enabled: true,
+                    name: '10.5% discount on order',
+                    couponCode: decimalPercentageCouponCode,
+                    conditions: [],
+                    actions: [
+                        {
+                            code: orderPercentageDiscount.code,
+                            arguments: [{ name: 'discount', value: '10.5' }],
+                        },
+                    ],
+                });
+                shopClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
+                const { addItemToOrder } = await shopClient.query<
+                    AddItemToOrder.Mutation,
+                    AddItemToOrder.Variables
+                >(ADD_ITEM_TO_ORDER, {
+                    productVariantId: getVariantBySlug('item-5000').id,
+                    quantity: 1,
+                });
+                orderResultGuard.assertSuccess(addItemToOrder);
+                expect(addItemToOrder!.totalWithTax).toBe(6000);
+                expect(addItemToOrder!.discounts.length).toBe(0);
+
+                const { applyCouponCode } = await shopClient.query<
+                    ApplyCouponCode.Mutation,
+                    ApplyCouponCode.Variables
+                >(APPLY_COUPON_CODE, {
+                    couponCode: decimalPercentageCouponCode,
+                });
+                orderResultGuard.assertSuccess(applyCouponCode);
+                expect(applyCouponCode!.discounts.length).toBe(1);
+                expect(applyCouponCode!.discounts[0].description).toBe('10.5% discount on order');
+                expect(applyCouponCode!.totalWithTax).toBe(5370);
+            });
         });
 
         describe('orderFixedDiscount', () => {

+ 1 - 1
packages/core/package.json

@@ -51,7 +51,7 @@
     "@types/fs-extra": "^9.0.1",
     "@vendure/common": "^1.7.4",
     "apollo-server-express": "2.24.1",
-    "bcrypt": "^5.0.0",
+    "bcrypt": "^5.1.0",
     "body-parser": "^1.19.0",
     "chalk": "^4.1.0",
     "commander": "^7.1.0",

+ 1 - 1
packages/core/src/config/promotion/actions/facet-values-percentage-discount-action.ts

@@ -10,7 +10,7 @@ export const discountOnItemWithFacets = new PromotionItemAction({
     code: 'facet_based_discount',
     args: {
         discount: {
-            type: 'int',
+            type: 'float',
             ui: {
                 component: 'number-form-input',
                 suffix: '%',

+ 2 - 1
packages/core/src/config/promotion/actions/order-fixed-discount-action.ts

@@ -13,7 +13,8 @@ export const orderFixedDiscount = new PromotionOrderAction({
         },
     },
     execute(ctx, order, args) {
-        return -Math.min(args.discount, order.subTotal);
+        const upperBound = ctx.channel.pricesIncludeTax ? order.subTotalWithTax : order.subTotal;
+        return -Math.min(args.discount, upperBound);
     },
     description: [{ languageCode: LanguageCode.en, value: 'Discount order by fixed amount' }],
 });

+ 1 - 1
packages/core/src/config/promotion/actions/order-percentage-discount-action.ts

@@ -6,7 +6,7 @@ export const orderPercentageDiscount = new PromotionOrderAction({
     code: 'order_percentage_discount',
     args: {
         discount: {
-            type: 'int',
+            type: 'float',
             ui: {
                 component: 'number-form-input',
                 suffix: '%',

+ 1 - 1
packages/core/src/config/promotion/actions/product-percentage-discount-action.ts

@@ -10,7 +10,7 @@ export const productsPercentageDiscount = new PromotionItemAction({
     description: [{ languageCode: LanguageCode.en, value: 'Discount specified products by { discount }%' }],
     args: {
         discount: {
-            type: 'int',
+            type: 'float',
             ui: {
                 component: 'number-form-input',
                 suffix: '%',

+ 14 - 1
packages/core/src/service/helpers/list-query-builder/list-query-builder.ts

@@ -406,8 +406,21 @@ export class ListQueryBuilder implements OnApplicationBootstrap {
         const entityMap = new Map(entities.map(e => [e.id, e]));
         const entitiesIds = entities.map(({ id }) => id);
 
-        const splitRelations = relations.map(r => r.split('.'));
+        const splitRelations = relations
+            .map(r => r.split('.'))
+            .filter(path => {
+                // There is an issue in TypeORM currently which causes
+                // an error when trying to join nested relations inside
+                // customFields. See https://github.com/vendure-ecommerce/vendure/issues/1664
+                // The work-around is to omit them and rely on the GraphQL resolver
+                // layer to handle.
+                if (path[0] === 'customFields' && 2 < path.length) {
+                    return false;
+                }
+                return true;
+            });
         const groupedRelationsMap = new Map<string, string[]>();
+
         for (const relationParts of splitRelations) {
             const group = groupedRelationsMap.get(relationParts[0]);
             if (group) {

+ 81 - 70
packages/elasticsearch-plugin/src/indexing/indexer.controller.ts

@@ -50,7 +50,6 @@ const REINDEX_CHUNK_SIZE = 2500;
 const REINDEX_OPERATION_CHUNK_SIZE = 3000;
 
 export const defaultProductRelations: Array<EntityRelationPaths<Product>> = [
-    'variants',
     'featuredAsset',
     'facetValues',
     'facetValues.facet',
@@ -515,79 +514,49 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
             Logger.error(e.message, loggerCtx, e.stack);
             throw e;
         }
-        if (product) {
-            const updatedProductVariants = await this.connection.getRepository(ProductVariant).findByIds(
-                product.variants.map(v => v.id),
-                {
-                    relations: this.variantRelations,
-                    where: {
-                        deletedAt: null,
-                    },
-                    order: {
-                        id: 'ASC',
-                    },
-                },
+        if (!product) {
+            return operations;
+        }
+        const updatedProductVariants = await this.connection.getRepository(ProductVariant).find({
+            relations: this.variantRelations,
+            where: {
+                productId,
+                deletedAt: null,
+            },
+            order: {
+                id: 'ASC',
+            },
+        });
+        // tslint:disable-next-line:no-non-null-assertion
+        updatedProductVariants.forEach(variant => (variant.product = product!));
+        if (!product.enabled) {
+            updatedProductVariants.forEach(v => (v.enabled = false));
+        }
+        Logger.debug(`Updating Product (${productId})`, loggerCtx);
+        const languageVariants: LanguageCode[] = [];
+        languageVariants.push(...product.translations.map(t => t.languageCode));
+        for (const variant of updatedProductVariants) {
+            languageVariants.push(...variant.translations.map(t => t.languageCode));
+        }
+        const uniqueLanguageVariants = unique(languageVariants);
+        for (const channel of product.channels) {
+            ctx.setChannel(channel);
+            const variantsInChannel = updatedProductVariants.filter(v =>
+                v.channels.map(c => c.id).includes(ctx.channelId),
             );
-            // tslint:disable-next-line:no-non-null-assertion
-            updatedProductVariants.forEach(variant => (variant.product = product!));
-            if (!product.enabled) {
-                updatedProductVariants.forEach(v => (v.enabled = false));
+            for (const variant of variantsInChannel) {
+                await this.productPriceApplicator.applyChannelPriceAndTax(variant, ctx);
             }
-            Logger.debug(`Updating Product (${productId})`, loggerCtx);
-            const languageVariants: LanguageCode[] = [];
-            languageVariants.push(...product.translations.map(t => t.languageCode));
-            for (const variant of product.variants) {
-                languageVariants.push(...variant.translations.map(t => t.languageCode));
-            }
-            const uniqueLanguageVariants = unique(languageVariants);
-
-            for (const channel of product.channels) {
-                ctx.setChannel(channel);
-
-                const variantsInChannel = updatedProductVariants.filter(v =>
-                    v.channels.map(c => c.id).includes(ctx.channelId),
-                );
-                for (const variant of variantsInChannel) {
-                    await this.productPriceApplicator.applyChannelPriceAndTax(variant, ctx);
-                }
-                for (const languageCode of uniqueLanguageVariants) {
-                    if (variantsInChannel.length) {
-                        for (const variant of variantsInChannel) {
-                            operations.push(
-                                {
-                                    index: VARIANT_INDEX_NAME,
-                                    operation: {
-                                        update: {
-                                            _id: ElasticsearchIndexerController.getId(
-                                                variant.id,
-                                                ctx.channelId,
-                                                languageCode,
-                                            ),
-                                        },
-                                    },
-                                },
-                                {
-                                    index: VARIANT_INDEX_NAME,
-                                    operation: {
-                                        doc: await this.createVariantIndexItem(
-                                            variant,
-                                            variantsInChannel,
-                                            ctx,
-                                            languageCode,
-                                        ),
-                                        doc_as_upsert: true,
-                                    },
-                                },
-                            );
-                        }
-                    } else {
+            for (const languageCode of uniqueLanguageVariants) {
+                if (variantsInChannel.length) {
+                    for (const variant of variantsInChannel) {
                         operations.push(
                             {
                                 index: VARIANT_INDEX_NAME,
                                 operation: {
                                     update: {
                                         _id: ElasticsearchIndexerController.getId(
-                                            -product.id,
+                                            variant.id,
                                             ctx.channelId,
                                             languageCode,
                                         ),
@@ -597,12 +566,39 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
                             {
                                 index: VARIANT_INDEX_NAME,
                                 operation: {
-                                    doc: this.createSyntheticProductIndexItem(product, ctx, languageCode),
+                                    doc: await this.createVariantIndexItem(
+                                        variant,
+                                        variantsInChannel,
+                                        ctx,
+                                        languageCode,
+                                    ),
                                     doc_as_upsert: true,
                                 },
                             },
                         );
                     }
+                } else {
+                    operations.push(
+                        {
+                            index: VARIANT_INDEX_NAME,
+                            operation: {
+                                update: {
+                                    _id: ElasticsearchIndexerController.getId(
+                                        -product.id,
+                                        ctx.channelId,
+                                        languageCode,
+                                    ),
+                                },
+                            },
+                        },
+                        {
+                            index: VARIANT_INDEX_NAME,
+                            operation: {
+                                doc: this.createSyntheticProductIndexItem(product, ctx, languageCode),
+                                doc_as_upsert: true,
+                            },
+                        },
+                    );
                 }
             }
         }
@@ -675,9 +671,24 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
                 .select('channel.id')
                 .getMany(),
         );
-        const product = await this.connection.getRepository(Product).findOne(productId, {
-            relations: ['variants'],
-        });
+
+        const product = await this.connection
+            .getRepository(ctx, Product)
+            .createQueryBuilder('product')
+            .select([
+                'product.id',
+                'productVariant.id',
+                'productTranslations.languageCode',
+                'productVariantTranslations.languageCode',
+            ])
+            .leftJoin('product.translations', 'productTranslations')
+            .leftJoin('product.variants', 'productVariant')
+            .leftJoin('productVariant.translations', 'productVariantTranslations')
+            .leftJoin('product.channels', 'channel')
+            .where('product.id = :productId', { productId })
+            .andWhere('channel.id = :channelId', { channelId: ctx.channelId })
+            .getOne();
+
         if (!product) {
             return [];
         }

+ 52 - 3
packages/email-plugin/src/plugin.ts

@@ -120,8 +120,57 @@ import {
  *
  * The `defaultEmailHandlers` array defines the default handlers such as for handling new account registration, order confirmation, password reset
  * etc. These defaults can be extended by adding custom templates for languages other than the default, or even completely new types of emails
- * which respond to any of the available [VendureEvents](/docs/typescript-api/events/). See the {@link EmailEventHandler} documentation for
- * details on how to do so.
+ * which respond to any of the available [VendureEvents](/docs/typescript-api/events/).
+ *
+ * A good way to learn how to create your own email handlers is to take a look at the
+ * [source code of the default handlers](https://github.com/vendure-ecommerce/vendure/blob/master/packages/email-plugin/src/default-email-handlers.ts).
+ * New handlers are defined in exactly the same way.
+ *
+ * It is also possible to modify the default handlers:
+ *
+ * ```TypeScript
+ * // Rather than importing `defaultEmailHandlers`, you can
+ * // import the handlers individually
+ * import {
+ *   orderConfirmationHandler,
+ *   emailVerificationHandler,
+ *   passwordResetHandler,
+ *   emailAddressChangeHandler,
+ * } from '\@vendure/email-plugin';
+ * import { CustomerService } from '\@vendure/core';
+ *
+ * // This allows you to then customize each handler to your needs.
+ * // For example, let's set a new subject line to the order confirmation:
+ * orderConfirmationHandler
+ *   .setSubject(`We received your order!`);
+ *
+ * // Another example: loading additional data and setting new
+ * // template variables.
+ * passwordResetHandler
+ *   .loadData(async ({ event, injector }) => {
+ *     const customerService = injector.get(CustomerService);
+ *     const customer = await customerService.findOneByUserId(event.ctx, event.user.id);
+ *     return { customer };
+ *   })
+ *   .setTemplateVars(event => ({
+ *     passwordResetToken: event.user.getNativeAuthenticationMethod().passwordResetToken,
+ *     customer: event.data.customer,
+ *   }));
+ *
+ * // Then you pass the handlers to the EmailPlugin init method
+ * // individually
+ * EmailPlugin.init({
+ *   handlers: [
+ *     orderConfirmationHandler,
+ *     emailVerificationHandler,
+ *     passwordResetHandler,
+ *     emailAddressChangeHandler,
+ *   ],
+ *   // ...
+ * }),
+ * ```
+ *
+ * For all available methods of extending a handler, see the {@link EmailEventHandler} documentation.
  *
  * ## Dev mode
  *
@@ -129,7 +178,7 @@ import {
  * file transport (See {@link FileTransportOptions}) and outputs emails as rendered HTML files in the directory specified by the
  * `outputPath` property.
  *
- * ```ts
+ * ```TypeScript
  * EmailPlugin.init({
  *   devMode: true,
  *   route: 'mailbox',

+ 75 - 17
yarn.lock

@@ -3154,20 +3154,20 @@
   resolved "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.3.1.tgz#3021ad0fa30a75a41212c5e7f1f169c5762ef8bb"
   integrity sha512-nOJARIr3pReqK3hfFCSW2Zg/kFcFsSAlIE7z4a0C9D2dPrgD/YSn3ZP2ET/rxKB65SXyG7jJbkynBRm+tGlacw==
 
-"@mapbox/node-pre-gyp@^1.0.0":
-  version "1.0.5"
-  resolved "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz#2a0b32fcb416fb3f2250fd24cb2a81421a4f5950"
-  integrity sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==
+"@mapbox/node-pre-gyp@^1.0.10":
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c"
+  integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==
   dependencies:
-    detect-libc "^1.0.3"
+    detect-libc "^2.0.0"
     https-proxy-agent "^5.0.0"
     make-dir "^3.1.0"
-    node-fetch "^2.6.1"
+    node-fetch "^2.6.7"
     nopt "^5.0.0"
-    npmlog "^4.1.2"
+    npmlog "^5.0.1"
     rimraf "^3.0.2"
-    semver "^7.3.4"
-    tar "^6.1.0"
+    semver "^7.3.5"
+    tar "^6.1.11"
 
 "@microsoft/fetch-event-source@2.0.1":
   version "2.0.1"
@@ -5211,6 +5211,14 @@ archy@^1.0.0:
   resolved "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40"
   integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=
 
+are-we-there-yet@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c"
+  integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==
+  dependencies:
+    delegates "^1.0.0"
+    readable-stream "^3.6.0"
+
 are-we-there-yet@^3.0.0:
   version "3.0.0"
   resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz#ba20bd6b553e31d62fc8c31bd23d22b95734390d"
@@ -5739,13 +5747,13 @@ bcrypt-pbkdf@^1.0.0:
   dependencies:
     tweetnacl "^0.14.3"
 
-bcrypt@^5.0.0:
-  version "5.0.1"
-  resolved "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz#f1a2c20f208e2ccdceea4433df0c8b2c54ecdf71"
-  integrity sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==
+bcrypt@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.1.0.tgz#bbb27665dbc400480a524d8991ac7434e8529e17"
+  integrity sha512-RHBS7HI5N5tEnGTmtR/pppX0mmDSBpQ4aCBsj7CEQfYXDcO74A8sIBYcJMuCsis2E81zDxeENYhv66oZwLiA+Q==
   dependencies:
-    "@mapbox/node-pre-gyp" "^1.0.0"
-    node-addon-api "^3.1.0"
+    "@mapbox/node-pre-gyp" "^1.0.10"
+    node-addon-api "^5.0.0"
 
 before-after-hook@^2.2.0:
   version "2.2.2"
@@ -9238,6 +9246,21 @@ function-bind@^1.1.1:
   resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
   integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
 
+gauge@^3.0.0:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395"
+  integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==
+  dependencies:
+    aproba "^1.0.3 || ^2.0.0"
+    color-support "^1.1.2"
+    console-control-strings "^1.0.0"
+    has-unicode "^2.0.1"
+    object-assign "^4.1.1"
+    signal-exit "^3.0.0"
+    string-width "^4.2.3"
+    strip-ansi "^6.0.1"
+    wide-align "^1.1.2"
+
 gauge@^4.0.0:
   version "4.0.1"
   resolved "https://registry.npmjs.org/gauge/-/gauge-4.0.1.tgz#82984bc08c90357d60b0a46c03a296beb1affec4"
@@ -13793,7 +13816,7 @@ node-abi@^3.3.0:
   dependencies:
     semver "^7.3.5"
 
-node-addon-api@^3.0.0, node-addon-api@^3.1.0:
+node-addon-api@^3.0.0:
   version "3.2.1"
   resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
   integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
@@ -13808,6 +13831,13 @@ node-fetch@2.6.1, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0, node-
   resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
   integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
 
+node-fetch@^2.6.7:
+  version "2.6.7"
+  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
+  integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
+  dependencies:
+    whatwg-url "^5.0.0"
+
 node-forge@^0.10.0:
   version "0.10.0"
   resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
@@ -14124,6 +14154,16 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1:
     gauge "~2.7.3"
     set-blocking "~2.0.0"
 
+npmlog@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0"
+  integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==
+  dependencies:
+    are-we-there-yet "^2.0.0"
+    console-control-strings "^1.1.0"
+    gauge "^3.0.0"
+    set-blocking "^2.0.0"
+
 npmlog@^6.0.0:
   version "6.0.1"
   resolved "https://registry.npmjs.org/npmlog/-/npmlog-6.0.1.tgz#06f1344a174c06e8de9c6c70834cfba2964bba17"
@@ -18146,7 +18186,7 @@ tar@^6.0.2, tar@^6.1.0:
     mkdirp "^1.0.3"
     yallist "^4.0.0"
 
-tar@^6.1.2:
+tar@^6.1.11, tar@^6.1.2:
   version "6.1.11"
   resolved "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
   integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
@@ -18419,6 +18459,11 @@ tr46@^2.1.0:
   dependencies:
     punycode "^2.1.1"
 
+tr46@~0.0.3:
+  version "0.0.3"
+  resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+  integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+
 traverse-chain@~0.1.0:
   version "0.1.0"
   resolved "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz#61dbc2d53b69ff6091a12a168fd7d433107e40f1"
@@ -19197,6 +19242,11 @@ webdriver-manager@^12.1.7:
     semver "^5.3.0"
     xml2js "^0.4.17"
 
+webidl-conversions@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+  integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
+
 webidl-conversions@^5.0.0:
   version "5.0.0"
   resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
@@ -19366,6 +19416,14 @@ whatwg-mimetype@^2.3.0:
   resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
   integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
 
+whatwg-url@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+  integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
+  dependencies:
+    tr46 "~0.0.3"
+    webidl-conversions "^3.0.0"
+
 whatwg-url@^8.0.0, whatwg-url@^8.4.0, whatwg-url@^8.5.0:
   version "8.7.0"
   resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77"