From 00ba03afb13488e1123a0ed0a860bd29c2b6541f Mon Sep 17 00:00:00 2001 From: Andy Brown Date: Fri, 2 Oct 2020 15:39:00 -0700 Subject: [PATCH] refactor: change method of installing extensions (#4315) * update default extensions directory * load builtin and remote extensions the same way * refactor installing remote extensions * update default extension manifest location * refactor removing an extension * add tests for npm * add search method to npm module * improve error logging * add tests for downloading extension * refactor manager tests * deprecate extendsComposer * add test for loadFromDir * add more manager tests * use npm registry api to search * omit installed extensions from search * add getBundle test * remove rimraf, use remove from fs-extra * make remvoing builtin a no-op * add rimraf back to dev deps * fix security warning by resolving to terser-webpack-plugin version * ensure extensions cannot be installed outside remoteDir * supress lgtm error --- Composer/.eslintrc.js | 1 + Composer/package.json | 3 +- Composer/packages/electron-server/src/main.ts | 2 +- Composer/packages/extension/package.json | 7 +- .../extension/src/extensionContext.ts | 2 +- .../src/manager/__tests__/manager.test.ts | 334 ++++++++++++++++-- .../packages/extension/src/manager/manager.ts | 223 ++++-------- .../packages/extension/src/types/extension.ts | 1 - .../utils/__tests__/isSubdirectory.test.ts | 27 ++ .../extension/src/utils/__tests__/npm.test.ts | 145 ++++++++ .../extension/src/utils/isSubdirectory.ts | 14 + Composer/packages/extension/src/utils/npm.ts | 117 +++--- Composer/packages/server/src/server.ts | 5 +- .../language-understanding/package.json | 1 - Composer/plugins/README.md | 2 +- Composer/plugins/authTest/package.json | 4 +- Composer/plugins/azurePublish/package.json | 1 - Composer/plugins/githubAuth/package.json | 4 +- Composer/plugins/localPublish/package.json | 1 - .../plugins/mockRemotePublish/package.json | 4 +- Composer/plugins/mongoStorage/package.json | 6 +- Composer/plugins/publishTest/package.json | 6 +- Composer/plugins/runtimes/package.json | 1 - Composer/plugins/samples/package.json | 1 - Composer/plugins/webRoute/package.json | 4 +- Composer/yarn.lock | 239 ++++--------- 26 files changed, 726 insertions(+), 429 deletions(-) create mode 100644 Composer/packages/extension/src/utils/__tests__/isSubdirectory.test.ts create mode 100644 Composer/packages/extension/src/utils/__tests__/npm.test.ts create mode 100644 Composer/packages/extension/src/utils/isSubdirectory.ts diff --git a/Composer/.eslintrc.js b/Composer/.eslintrc.js index b0e4f3bd7f..9924da904d 100644 --- a/Composer/.eslintrc.js +++ b/Composer/.eslintrc.js @@ -105,6 +105,7 @@ module.exports = { '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-object-literal-type-assertion': 'off', '@typescript-eslint/unbound-method': 'off', + '@typescript-eslint/no-explicit-any': 'off', 'security/detect-buffer-noassert': 'off', 'security/detect-child-process': 'off', diff --git a/Composer/package.json b/Composer/package.json index f73d7a06f0..42088ce4f8 100644 --- a/Composer/package.json +++ b/Composer/package.json @@ -12,7 +12,8 @@ "mkdirp": "^0.5.2", "selfsigned": "1.10.8", "serialize-javascript": "^3.1.0", - "set-value": "^3.0.2" + "set-value": "^3.0.2", + "terser-webpack-plugin": "^2.3.7" }, "engines": { "node": ">=12" diff --git a/Composer/packages/electron-server/src/main.ts b/Composer/packages/electron-server/src/main.ts index 3887eda3b0..5b9f4c14f5 100644 --- a/Composer/packages/electron-server/src/main.ts +++ b/Composer/packages/electron-server/src/main.ts @@ -63,7 +63,7 @@ async function createAppDataDir() { const azurePublishPath: string = join(composerAppDataPath, 'publishBots'); process.env.COMPOSER_APP_DATA = join(composerAppDataPath, 'data.json'); // path to the actual data file process.env.COMPOSER_EXTENSION_DATA = join(composerAppDataPath, 'extensions.json'); - process.env.COMPOSER_REMOTE_EXTENSIONS_DIR = join(composerAppDataPath, '.composer'); + process.env.COMPOSER_REMOTE_EXTENSIONS_DIR = join(composerAppDataPath, 'extensions'); log('creating composer app data path at: ', composerAppDataPath); diff --git a/Composer/packages/extension/package.json b/Composer/packages/extension/package.json index cd99329ead..e10292e83c 100644 --- a/Composer/packages/extension/package.json +++ b/Composer/packages/extension/package.json @@ -8,7 +8,7 @@ "scripts": { "build": "yarn build:clean && yarn build:ts", "build:ts": "tsc -p tsconfig.build.json", - "build:clean": "rimraf lib && rimraf build", + "build:clean": "rimraf lib", "lint": "eslint --quiet ./src", "test": "jest" }, @@ -18,6 +18,7 @@ "@types/fs-extra": "^9.0.1", "@types/passport": "^1.0.3", "@types/path-to-regexp": "^1.7.0", + "@types/tar": "^4.0.3", "json-schema": "^0.2.5", "rimraf": "^3.0.2", "typescript": "^3.8.3" @@ -26,7 +27,9 @@ "debug": "^4.1.1", "fs-extra": "^9.0.1", "globby": "^11.0.0", + "node-fetch": "^2.6.1", "passport": "^0.4.1", - "path-to-regexp": "^6.1.0" + "path-to-regexp": "^6.1.0", + "tar": "^6.0.5" } } diff --git a/Composer/packages/extension/src/extensionContext.ts b/Composer/packages/extension/src/extensionContext.ts index 3ece1f7681..5df54cb426 100644 --- a/Composer/packages/extension/src/extensionContext.ts +++ b/Composer/packages/extension/src/extensionContext.ts @@ -88,7 +88,7 @@ class ExtensionContext { const packageJSON = fs.readFileSync(packageJsonPath, 'utf8'); const json = JSON.parse(packageJSON); - if (json.extendsComposer) { + if (json.composer?.enabled !== false) { const modulePath = path.dirname(packageJsonPath); try { // eslint-disable-next-line security/detect-non-literal-require, @typescript-eslint/no-var-requires diff --git a/Composer/packages/extension/src/manager/__tests__/manager.test.ts b/Composer/packages/extension/src/manager/__tests__/manager.test.ts index 2f80727d89..27ea3be5b2 100644 --- a/Composer/packages/extension/src/manager/__tests__/manager.test.ts +++ b/Composer/packages/extension/src/manager/__tests__/manager.test.ts @@ -1,15 +1,25 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import path from 'path'; -import { writeJsonSync } from 'fs-extra'; +import { readJson, ensureDir, remove } from 'fs-extra'; +import glob from 'globby'; -import { ExtensionManager } from '../manager'; +import { search, downloadPackage } from '../../utils/npm'; +import { ExtensionManifestStore, ExtensionManifest } from '../../storage/extensionManifestStore'; +import { ExtensionManagerImp } from '../manager'; -const mockManifest = { +const mockManifest = ({ extension1: { id: 'extension1', builtIn: true, enabled: true, + bundles: [ + { + id: 'bundleId', + path: '/some/path', + }, + ], }, extension2: { id: 'extension2', @@ -19,20 +29,42 @@ const mockManifest = { id: 'extension3', enabled: false, }, -}; +} as unknown) as ExtensionManifest; + +jest.mock('../../storage/extensionManifestStore'); + +jest.mock('globby', () => jest.fn()); + +jest.mock('fs-extra', () => ({ + ensureDir: jest.fn(), + readJson: jest.fn(), + remove: jest.fn(), +})); + +jest.mock('../../utils/npm'); + +let manager: ExtensionManagerImp; +let manifest: ExtensionManifestStore; beforeEach(() => { - writeJsonSync(process.env.COMPOSER_EXTENSION_DATA as string, mockManifest); - ExtensionManager.reloadManifest(); + manifest = new ExtensionManifestStore('/some/path'); }); describe('#getAll', () => { it('return an array of all extensions', () => { - expect(ExtensionManager.getAll()).toEqual([ + (manifest.getExtensions as jest.Mock).mockReturnValue(mockManifest); + manager = new ExtensionManagerImp(manifest); + expect(manager.getAll()).toEqual([ { id: 'extension1', builtIn: true, enabled: true, + bundles: [ + { + id: 'bundleId', + path: '/some/path', + }, + ], }, { id: 'extension2', @@ -48,32 +80,292 @@ describe('#getAll', () => { describe('#find', () => { it('returns extension metadata for id', () => { - expect(ExtensionManager.find('extension1')).toEqual({ id: 'extension1', builtIn: true, enabled: true }); - expect(ExtensionManager.find('does-not-exist')).toBeUndefined(); + (manifest.getExtensionConfig as jest.Mock).mockImplementation((id) => { + return mockManifest[id]; + }); + manager = new ExtensionManagerImp(manifest); + expect(manager.find('extension1')).toEqual({ + id: 'extension1', + builtIn: true, + enabled: true, + bundles: [ + { + id: 'bundleId', + path: '/some/path', + }, + ], + }); + expect(manager.find('does-not-exist')).toBeUndefined(); }); }); describe('#loadAll', () => { - it('loads built-in extensions and remote extensions that are enabled', async () => { - const loadSpy = jest.spyOn(ExtensionManager, 'load'); + let loadSpy: jest.SpyInstance; + + beforeEach(() => { + manager = new ExtensionManagerImp(manifest); + loadSpy = jest.spyOn(manager, 'loadFromDir'); loadSpy.mockReturnValue(Promise.resolve()); + }); + + it('ensures remote dir is created', async () => { + await manager.loadAll(); + + expect(ensureDir).toHaveBeenCalledWith(process.env.COMPOSER_REMOTE_EXTENSIONS_DIR); + }); - await ExtensionManager.loadAll(); + it('loads built-in extensions and remote extensions that are enabled', async () => { + await manager.loadAll(); expect(loadSpy).toHaveBeenCalledTimes(2); + expect(loadSpy).toHaveBeenNthCalledWith(1, process.env.COMPOSER_BUILTIN_EXTENSIONS_DIR, true); + expect(loadSpy).toHaveBeenNthCalledWith(2, process.env.COMPOSER_REMOTE_EXTENSIONS_DIR); + }); +}); + +describe('#loadFromDir', () => { + it('finds all package.json files in dir', async () => { + ((glob as unknown) as jest.Mock).mockReturnValue([]); + manager = new ExtensionManagerImp(manifest); + + await manager.loadFromDir('/some/dir'); + expect(glob).toHaveBeenCalledWith('*/package.json', { cwd: '/some/dir' }); + }); + + it('updates the extension manifest and loads each extension found', async () => { + ((glob as unknown) as jest.Mock).mockReturnValue(['extension1/package.json', 'extension2/package.json']); + (readJson as jest.Mock).mockImplementation((path) => { + if (path.includes('extension1')) { + return { + name: 'extension1', + }; + } else if (path.includes('extension2')) { + return { + name: 'extension2', + }; + } + + return {}; + }); + + manager = new ExtensionManagerImp(manifest); + const loadSpy = jest.spyOn(manager, 'load'); + loadSpy.mockResolvedValue(undefined); + + await manager.loadFromDir('/some/dir'); + + expect(readJson).toHaveBeenCalledWith('/some/dir/extension1/package.json'); + expect(readJson).toHaveBeenCalledWith('/some/dir/extension2/package.json'); + + expect(manifest.updateExtensionConfig).toHaveBeenCalledWith( + 'extension1', + expect.objectContaining({ id: 'extension1' }) + ); + expect(manifest.updateExtensionConfig).toHaveBeenCalledWith( + 'extension2', + expect.objectContaining({ id: 'extension2' }) + ); + expect(loadSpy).toHaveBeenCalledWith('extension1'); expect(loadSpy).toHaveBeenCalledWith('extension2'); }); + + it('removes the extension from the manifest if not enabled', async () => { + ((glob as unknown) as jest.Mock).mockReturnValue(['extension1/package.json']); + (readJson as jest.Mock).mockResolvedValue({ + name: 'extension1', + composer: { + enabled: false, + }, + }); + (manifest.getExtensionConfig as jest.Mock).mockReturnValueOnce('extension1'); + + manager = new ExtensionManagerImp(manifest); + const loadSpy = jest.spyOn(manager, 'load'); + loadSpy.mockResolvedValue(undefined); + + await manager.loadFromDir('/some/dir'); + + expect(manifest.updateExtensionConfig).not.toHaveBeenCalled(); + expect(loadSpy).not.toHaveBeenCalled(); + expect(manifest.removeExtension).toHaveBeenCalledTimes(1); + expect(manifest.removeExtension).toHaveBeenCalledWith('extension1'); + }); +}); + +describe('#installRemote', () => { + it('ensures remote dir exists', async () => { + manager = new ExtensionManagerImp(manifest); + await manager.installRemote('extension1'); + + expect(ensureDir).toHaveBeenLastCalledWith(process.env.COMPOSER_REMOTE_EXTENSIONS_DIR); + }); + + it('validates destination directory', () => { + manager = new ExtensionManagerImp(manifest); + expect(manager.installRemote('../extension')).rejects.toThrow(); + expect(manager.installRemote('../../extension')).rejects.toThrow(); + }); + + it('downloads package and updates the manifest', async () => { + (readJson as jest.Mock).mockResolvedValue({ + name: 'extension1', + }); + + manager = new ExtensionManagerImp(manifest); + await manager.installRemote('extension1'); + + expect(downloadPackage).toHaveBeenCalledWith( + 'extension1', + 'latest', + path.join(process.env.COMPOSER_REMOTE_EXTENSIONS_DIR as string, 'extension1') + ); + + expect(manifest.updateExtensionConfig).toHaveBeenCalledWith( + 'extension1', + expect.objectContaining({ id: 'extension1' }) + ); + }); + + it('throws an error if problem downloading', () => { + (downloadPackage as jest.Mock).mockRejectedValue(undefined); + manager = new ExtensionManagerImp(manifest); + + expect(manager.installRemote('extension1', '2.0.0')).rejects.toThrow(/Unable to install/); + }); }); -// describe('#installRemote', () => {}); -// describe('#loadBuiltinExtensions', () => {}); -// describe('#loadRemotePlugins', () => {}); // describe('#load', () => {}); -// describe('#enable', () => {}); -// describe('#disable', () => {}); -// describe('#remove', () => {}); -// describe('#search', () => {}); -// describe('#getAllBundles', () => {}); -// describe('#getBundle', () => {}); + +describe('#enable', () => { + it('updates the manifest and reloads the extension', async () => { + manager = new ExtensionManagerImp(manifest); + const loadSpy = jest.spyOn(manager, 'load'); + (loadSpy as jest.Mock).mockResolvedValue(undefined); + + await manager.enable('extension1'); + + expect(manifest.updateExtensionConfig).toHaveBeenCalledWith('extension1', { enabled: true }); + expect(loadSpy).toHaveBeenCalledWith('extension1'); + }); +}); + +describe('#disable', () => { + it('updates the manifest', async () => { + manager = new ExtensionManagerImp(manifest); + + await manager.disable('extension1'); + + expect(manifest.updateExtensionConfig).toHaveBeenCalledWith('extension1', { enabled: false }); + }); +}); + +describe('#remove', () => { + it('throws an error if extension not found', () => { + (manifest.getExtensionConfig as jest.Mock).mockReturnValue(undefined); + manager = new ExtensionManagerImp(manifest); + + expect(manager.remove('extension1')).rejects.toThrow(/Unable to remove extension/); + }); + + it('is a no-op if the extension is builtin', async () => { + (manifest.getExtensionConfig as jest.Mock).mockReturnValue({ builtIn: true }); + manager = new ExtensionManagerImp(manifest); + + await manager.remove('extension1'); + + expect(remove).not.toHaveBeenCalled(); + expect(manifest.removeExtension).not.toHaveBeenCalled(); + }); + + it('removes the extension from the manifest and cleans up', async () => { + (manifest.getExtensionConfig as jest.Mock).mockReturnValue({ builtIn: false, path: '/some/path' }); + manager = new ExtensionManagerImp(manifest); + + await manager.remove('extension1'); + + expect(remove).toHaveBeenCalledWith('/some/path'); + expect(manifest.removeExtension).toHaveBeenCalledWith('extension1'); + }); +}); + +describe('#search', () => { + beforeEach(() => { + (search as jest.Mock).mockResolvedValue([ + { + id: 'extension1', + keywords: ['botframework-composer', 'extension', 'foo', 'bar'], + description: 'LOREM ipsum', + version: '1.0.0', + url: 'extension1 npm link', + }, + { + id: 'extension-2', + keywords: ['botframework-composer', 'extension', 'bar'], + description: 'foo', + version: '1.0.0', + url: 'extension-2 npm link', + }, + { + id: 'foo', + keywords: ['botframework-composer'], + description: 'lorem ipsum', + version: '1.0.0', + url: 'foo npm link', + }, + { + id: 'bar', + keywords: ['botframework-composer'], + description: '', + version: '1.0.0', + url: 'bar npm link', + }, + ]); + }); + + it('filters the search results by the extension keyword', async () => { + manager = new ExtensionManagerImp(manifest); + + const results = await manager.search('foo'); + + expect(search).toHaveBeenCalledWith('foo'); + expect(results).toHaveLength(2); + }); + + it('omits currently installed extensions', async () => { + (manifest.getExtensionConfig as jest.Mock).mockImplementation((id) => { + return mockManifest[id]; + }); + + manager = new ExtensionManagerImp(manifest); + + const results = await manager.search('foo'); + + expect(search).toHaveBeenCalledWith('foo'); + expect(results).toHaveLength(1); + }); +}); + +describe('#getBundle', () => { + beforeEach(() => { + (manifest.getExtensionConfig as jest.Mock).mockImplementation((id) => { + return mockManifest[id]; + }); + }); + + it('throws an error if extension not found', () => { + manager = new ExtensionManagerImp(manifest); + expect(() => manager.getBundle('does-not-exist', 'bundleId')).toThrow('extension not found'); + }); + + it('throws an error if bundle not found', () => { + manager = new ExtensionManagerImp(manifest); + expect(() => manager.getBundle('extension1', 'does-not-exist')).toThrow('bundle not found'); + }); + + it('returns the bundle path', () => { + manager = new ExtensionManagerImp(manifest); + expect(manager.getBundle('extension1', 'bundleId')).toEqual('/some/path'); + }); +}); diff --git a/Composer/packages/extension/src/manager/manager.ts b/Composer/packages/extension/src/manager/manager.ts index 1fd4578297..74cc9fba1f 100644 --- a/Composer/packages/extension/src/manager/manager.ts +++ b/Composer/packages/extension/src/manager/manager.ts @@ -4,18 +4,17 @@ import path from 'path'; import glob from 'globby'; -import { readJson, ensureDir, existsSync } from 'fs-extra'; +import { readJson, ensureDir, remove } from 'fs-extra'; import { ExtensionContext } from '../extensionContext'; import logger from '../logger'; import { ExtensionManifestStore } from '../storage/extensionManifestStore'; -import { ExtensionBundle, PackageJSON, ExtensionMetadata, ExtensionSearchResult } from '../types/extension'; -import { npm } from '../utils/npm'; +import { ExtensionBundle, PackageJSON, ExtensionMetadata } from '../types/extension'; +import { search, downloadPackage } from '../utils/npm'; +import { isSubdirectory } from '../utils/isSubdirectory'; const log = logger.extend('manager'); -const SEARCH_CACHE_TIMEOUT = 5 * 60000; // 5 minutes - function processBundles(extensionPath: string, bundles: ExtensionBundle[]) { return bundles.map((b) => ({ ...b, @@ -36,10 +35,8 @@ function getExtensionMetadata(extensionPath: string, packageJson: PackageJSON): }; } -class ExtensionManager { - private searchCache = new Map(); - private _manifest: ExtensionManifestStore | undefined; - private _lastSearchTimestamp: Date | undefined; +export class ExtensionManagerImp { + public constructor(private _manifest?: ExtensionManifestStore) {} /** * Returns all extensions currently in the extension manifest @@ -61,14 +58,35 @@ class ExtensionManager { * Loads all builtin extensions and remote extensions. */ public async loadAll() { - await this.seedBuiltinExtensions(); await ensureDir(this.remoteDir); - const extensions = Object.entries(this.manifest.getExtensions()); + await this.loadFromDir(this.builtinDir, true); + await this.loadFromDir(this.remoteDir); + } - for (const [id, metadata] of extensions) { - if (metadata?.enabled) { - await this.load(id); + /** + * Loads extensions from a given directory + * @param dir directory to load extensions from + * @param isBuiltin used to set extension metadata + */ + public async loadFromDir(dir: string, isBuiltin = false) { + log('Loading extensions from %s', dir); + const extensions = await glob('*/package.json', { cwd: dir }); + for (const extensionPackageJsonPath of extensions) { + const fullPath = path.join(dir, extensionPackageJsonPath); + const extensionInstallPath = path.dirname(fullPath); + const packageJson = (await readJson(fullPath)) as PackageJSON; + const isEnabled = packageJson.composer?.enabled !== false; + const metadata = getExtensionMetadata(extensionInstallPath, packageJson); + if (isEnabled) { + this.manifest.updateExtensionConfig(metadata.id, { + ...metadata, + builtIn: isBuiltin, + }); + await this.load(metadata.id); + } else if (this.manifest.getExtensionConfig(metadata.id)) { + // remove the extension if it exists in the manifest + this.manifest.removeExtension(metadata.id); } } } @@ -80,65 +98,28 @@ class ExtensionManager { * @returns id of installed package */ public async installRemote(name: string, version?: string) { + await ensureDir(this.remoteDir); const packageNameAndVersion = version ? `${name}@${version}` : `${name}@latest`; log('Installing %s to %s', packageNameAndVersion, this.remoteDir); try { - const { stdout } = await npm( - 'install', - packageNameAndVersion, - { '--prefix': this.remoteDir }, - { cwd: this.remoteDir } - ); - - log('%s', stdout); - - const packageJson = await this.getPackageJson(name, this.remoteDir); - - if (packageJson) { - const extensionPath = path.resolve(this.remoteDir, 'node_modules', name); - this.manifest.updateExtensionConfig(name, getExtensionMetadata(extensionPath, packageJson)); + const destination = path.join(this.remoteDir, name); - return packageJson.name; - } else { - throw new Error(`Unable to install ${packageNameAndVersion}`); + if (!isSubdirectory(this.remoteDir, destination)) { + throw new Error('Cannot install outside of the configured directory.'); } - } catch (err) { - if (err?.stderr) { - log('%s', err.stderr); - } - throw new Error(`Unable to install ${packageNameAndVersion}`); - } - } - /** - * Installs a local extension at path - * @param path Path of directory where extension is - */ - public async installLocal(extPath: string) { - try { - const packageJsonPath = path.join(extPath, 'package.json'); + await downloadPackage(name, version ?? 'latest', destination); - if (!existsSync(packageJsonPath)) { - throw new Error(`Extension not found at path: ${extPath}`); + const packageJson = await this.getPackageJson(name, this.remoteDir); + if (packageJson) { + this.manifest.updateExtensionConfig(packageJson.name, getExtensionMetadata(destination, packageJson)); } - const packageJson = await readJson(packageJsonPath); - - log('Linking %s', packageJson.name); - await npm('link', '.', {}, { cwd: extPath }); - - log('Installing %s@local to %s', packageJson.name, this.remoteDir); - await npm('link', packageJson.name, { '--prefix': this.remoteDir }, { cwd: this.remoteDir }); - - const extensionPath = path.resolve(this.remoteDir, 'node_modules', packageJson.name); - this.manifest.updateExtensionConfig(packageJson.name, getExtensionMetadata(extensionPath, packageJson)); - - return packageJson.name; + return name; } catch (err) { - log('%s', err.msg ?? err.stderr ?? err); - // eslint-disable-next-line no-console - console.error(err); + log('%O', err); + throw new Error(`Unable to install ${packageNameAndVersion}`); } } @@ -190,14 +171,16 @@ class ExtensionManager { public async remove(id: string) { log('Removing %s', id); - try { - const { stdout } = await npm('uninstall', id, { '--prefix': this.remoteDir }, { cwd: this.remoteDir }); + const metadata = this.find(id); - log('%s', stdout); + if (metadata) { + if (metadata.builtIn) { + return; + } + await remove(metadata.path); this.manifest.removeExtension(id); - } catch (err) { - log('%s', err); + } else { throw new Error(`Unable to remove extension: ${id}`); } } @@ -207,33 +190,12 @@ class ExtensionManager { * @param query The search query */ public async search(query: string) { - await this.updateSearchCache(); - const normalizedQuery = query.toLowerCase(); - - const results = Array.from(this.searchCache.values()).filter((result) => { - return ( - !this.find(result.id) && - [result.id, result.description, ...result.keywords].some((target) => - target.toLowerCase().includes(normalizedQuery) - ) - ); - }); - - return results; - } - - /** - * Returns a list of all of an extension's bundles - * @param id The ID of the extension for which we will fetch the list of bundles - */ - public async getAllBundles(id: string) { - const info = this.find(id); + const results = await search(query); - if (!info) { - throw new Error('extension not found'); - } - - return info.bundles ?? []; + return results.filter((searchResult) => { + const { id, keywords } = searchResult; + return !this.find(id) && keywords.includes('extension'); + }); } /** @@ -259,52 +221,27 @@ class ExtensionManager { private async getPackageJson(id: string, dir: string): Promise { try { - const extensionPackagePath = path.resolve(dir, 'node_modules', id, 'package.json'); + const extensionPackagePath = path.resolve(dir, id, 'package.json'); log('fetching package.json for %s at %s', id, extensionPackagePath); const packageJson = await readJson(extensionPackagePath); return packageJson as PackageJSON; - } catch (err) { + } catch (err) /* istanbul ignore next */ { log('Error getting package json for %s', id); - // eslint-disable-next-line no-console - console.error(err); - } - } - - public async seedBuiltinExtensions() { - const extensions = await glob('*/package.json', { cwd: this.builtinDir, dot: true }); - for (const extensionPackageJsonPath of extensions) { - // go through each extension, make sure to add it to the manager store then load it as usual - const fullPath = path.join(this.builtinDir, extensionPackageJsonPath); - const extensionInstallPath = path.dirname(fullPath); - const packageJson = (await readJson(fullPath)) as PackageJSON; - const isEnabled = packageJson?.composer && packageJson.composer.enabled !== false; - const metadata = getExtensionMetadata(extensionInstallPath, packageJson); - if (packageJson && (isEnabled || packageJson.extendsComposer === true)) { - this.manifest.updateExtensionConfig(packageJson.name, { - ...metadata, - builtIn: true, - }); - } else if (this.manifest.getExtensionConfig(packageJson.name)) { - // remove the extension if it exists in the manifest - this.manifest.removeExtension(packageJson.name); - } + log('%O', err); } } - public reloadManifest() { - this._manifest = undefined; - } - private get manifest() { - if (this._manifest) { - return this._manifest; + /* istanbul ignore next */ + if (!this._manifest) { + this._manifest = new ExtensionManifestStore(process.env.COMPOSER_EXTENSION_DATA as string); } - this._manifest = new ExtensionManifestStore(process.env.COMPOSER_EXTENSION_DATA as string); return this._manifest; } private get builtinDir() { + /* istanbul ignore next */ if (!process.env.COMPOSER_BUILTIN_EXTENSIONS_DIR) { throw new Error('COMPOSER_BUILTIN_EXTENSIONS_DIR must be set.'); } @@ -313,45 +250,15 @@ class ExtensionManager { } private get remoteDir() { + /* istanbul ignore next */ if (!process.env.COMPOSER_REMOTE_EXTENSIONS_DIR) { throw new Error('COMPOSER_REMOTE_EXTENSIONS_DIR must be set.'); } return process.env.COMPOSER_REMOTE_EXTENSIONS_DIR; } - - private async updateSearchCache() { - const timeout = new Date(new Date().getTime() - SEARCH_CACHE_TIMEOUT); - if (!this._lastSearchTimestamp || this._lastSearchTimestamp < timeout) { - const { stdout } = await npm('search', '', { - '--json': '', - '--searchopts': '"keywords:botframework-composer extension"', - }); - - try { - const result = JSON.parse(stdout); - if (Array.isArray(result)) { - result.forEach((searchResult) => { - const { name, keywords = [], version, description, links } = searchResult; - if (keywords.includes('botframework-composer') && keywords.includes('extension')) { - const url = links?.npm ?? ''; - this.searchCache.set(name, { - id: name, - version, - description, - keywords, - url, - }); - } - }); - } - } catch (err) { - log('%O', err); - } - } - } } -const manager = new ExtensionManager(); +const ExtensionManager = new ExtensionManagerImp(); -export { manager as ExtensionManager }; +export { ExtensionManager }; diff --git a/Composer/packages/extension/src/types/extension.ts b/Composer/packages/extension/src/types/extension.ts index c13ecc9bdb..6104fab5b0 100644 --- a/Composer/packages/extension/src/types/extension.ts +++ b/Composer/packages/extension/src/types/extension.ts @@ -62,7 +62,6 @@ export interface PackageJSON { name: string; version: string; description: string; - extendsComposer: boolean; composer?: { name?: string; enabled?: boolean; diff --git a/Composer/packages/extension/src/utils/__tests__/isSubdirectory.test.ts b/Composer/packages/extension/src/utils/__tests__/isSubdirectory.test.ts new file mode 100644 index 0000000000..39139c6195 --- /dev/null +++ b/Composer/packages/extension/src/utils/__tests__/isSubdirectory.test.ts @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { isSubdirectory } from '../isSubdirectory'; + +const testCases = [ + ['/foo/bar', '/foo/bar', false], + ['/foo/bar', '/bar/foo', false], + ['/foo/bar', '/foo/bar/../baz', false], + ['/foo/bar', '/foo/bar/../', false], + ['/foo/bar', '/foo/bar/baz/../', false], + ['/foo/bar', '/foo/baz/bar/', false], + ['/foo/bar', '/foo', false], + ['/foo/bar', '/', false], + + ['/foo/bar', '/foo/bar/baz', true], + ['/foo/bar', '/foo/bar/baz/../qux', true], + ['/foo/bar', '/foo/bar/baz/qux/foo/../../bar', true], +]; + +if (process.platform === 'win32') { + testCases.push(['C:\\Foo', 'C:\\Foo\\Bar', true], ['C:\\Foo', 'C:\\Bar', false], ['C:\\Foo', 'D:\\Foo\\Bar', false]); +} + +it.each(testCases)('isSubDir(%s, %s) -> %s', (parent, dir, expected) => { + expect(isSubdirectory(parent as string, dir as string)).toBe(expected); +}); diff --git a/Composer/packages/extension/src/utils/__tests__/npm.test.ts b/Composer/packages/extension/src/utils/__tests__/npm.test.ts new file mode 100644 index 0000000000..fb9d55f6bd --- /dev/null +++ b/Composer/packages/extension/src/utils/__tests__/npm.test.ts @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +/* eslint-disable no-underscore-dangle */ +import { Readable, Writable } from 'stream'; + +import fetch from 'node-fetch'; +import { mkdir, remove } from 'fs-extra'; +import tar from 'tar'; + +import { search, downloadPackage } from '../npm'; + +class MockBody extends Readable { + _read() { + setTimeout(() => { + this.emit('end'); + }, 1); + } +} + +class MockExtractor extends Writable { + _write(chunk: any, enc: string, next) { + next(); + } +} + +jest.mock('node-fetch', () => jest.fn()); +jest.mock('fs-extra', () => ({ + mkdir: jest.fn(), + remove: jest.fn(), +})); +jest.mock('tar', () => ({ + extract: jest.fn(), +})); + +describe('search', () => { + const data = [ + { + name: 'package1', + description: 'package1 description', + version: '0.0.1', + keywords: ['foo'], + links: { npm: 'package1 npm link' }, + }, + { name: 'package2', description: 'package2 description', version: '0.0.2' }, + ]; + + beforeEach(() => { + const mockRes = { + json: jest.fn(), + }; + ((fetch as unknown) as jest.Mock).mockImplementation(() => mockRes); + mockRes.json.mockResolvedValue({ objects: data.map((d) => ({ package: d })) }); + }); + + it('returns results of npm search', async () => { + const results = await search('my query'); + + expect(fetch).toHaveBeenCalledWith( + expect.stringContaining('registry.npmjs.org/-/v1/search?text=my+query+keywords:botframework-composer') + ); + + expect(results).toEqual([ + { + id: 'package1', + description: 'package1 description', + version: '0.0.1', + keywords: ['foo'], + url: 'package1 npm link', + }, + { + id: 'package2', + description: 'package2 description', + version: '0.0.2', + keywords: [], + url: '', + }, + ]); + }); +}); + +describe('downloadPackage', () => { + const packageName = 'some-package'; + + const packageMetadata = { + 'dist-tags': { + latest: '1.0.1', + }, + versions: { + '1.0.0': { + dist: { + tarball: 'tarball url 1.0.0', + }, + }, + '1.0.1': { + dist: { + tarball: 'tarball url 1.0.1', + }, + }, + }, + }; + + beforeEach(() => { + const mockRes = { + json: jest.fn(), + body: new MockBody(), + }; + ((fetch as unknown) as jest.Mock).mockImplementation(() => mockRes); + mockRes.json.mockResolvedValue(packageMetadata); + + const extractor = new MockExtractor(); + (tar.extract as jest.Mock).mockReturnValue(extractor); + + mockRes.body.push('some data'); + }); + + it('gets package metadata from npm', async () => { + await downloadPackage(packageName, 'latest', '/some/path'); + + expect(fetch).toHaveBeenCalledWith('https://registry.npmjs.org/some-package'); + }); + + it.each([ + ['latest', '1.0.1'], + ['1.0.0', '1.0.0'], + ])('can resolve %s to %s', async (tagOrVersion, expectedVersion) => { + await downloadPackage(packageName, tagOrVersion, '/some/path'); + expect(fetch).toHaveBeenCalledWith(`tarball url ${expectedVersion}`); + }); + + it('throws when version not found', () => { + expect(downloadPackage(packageName, '2.0.0', '/some/path')).rejects.toThrow(/Could not find/); + }); + + it('prepares the destination directory', async () => { + await downloadPackage(packageName, 'latest', '/some/path'); + expect(remove).toHaveBeenCalledWith('/some/path'); + expect(mkdir).toHaveBeenCalledWith('/some/path'); + }); + + it('extracts the tarball into destination', async () => { + await downloadPackage(packageName, 'latest', '/some/path'); + + expect(tar.extract).toHaveBeenCalledWith({ strip: 1, C: '/some/path', strict: true }); + }); +}); diff --git a/Composer/packages/extension/src/utils/isSubdirectory.ts b/Composer/packages/extension/src/utils/isSubdirectory.ts new file mode 100644 index 0000000000..eb1c4f5c08 --- /dev/null +++ b/Composer/packages/extension/src/utils/isSubdirectory.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import path from 'path'; + +/** + * Returns true if dir is a descendant of parent, false otherwise. + * @param parent parent directory + * @param dir directory to test + */ +export function isSubdirectory(parent, dir) { + const relative = path.relative(parent, dir); + return Boolean(relative && !relative.startsWith('..') && !path.isAbsolute(relative)); +} diff --git a/Composer/packages/extension/src/utils/npm.ts b/Composer/packages/extension/src/utils/npm.ts index 60685b7c98..79dfcadeaf 100644 --- a/Composer/packages/extension/src/utils/npm.ts +++ b/Composer/packages/extension/src/utils/npm.ts @@ -1,65 +1,80 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { spawn, SpawnOptionsWithoutStdio } from 'child_process'; +import { promisify } from 'util'; + +import { mkdir, remove } from 'fs-extra'; +import fetch from 'node-fetch'; +import tar from 'tar'; import logger from '../logger'; +import { ExtensionSearchResult } from '../types/extension'; + +const streamPipeline = promisify(require('stream').pipeline); const log = logger.extend('npm'); -type NpmOutput = { - stdout: string; - stderr: string; - code: number; -}; -type NpmCommand = 'install' | 'uninstall' | 'search' | 'link'; -type NpmOptions = { - [key: string]: string; -}; - -function processOptions(opts: NpmOptions) { - return Object.entries({ '--no-fund': '', '--no-audit': '', '--quiet': '', ...opts }).map(([flag, value]) => { - return value ? `${flag}=${value}` : flag; - }); -} +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function search(query = ''): Promise { + try { + log('Searching for %s', query); + const queryString = query.replace(' ', '+'); + const res = await fetch( + `https://registry.npmjs.org/-/v1/search?text=${queryString}+keywords:botframework-composer&size=100&from=0&quality=0.65&popularity=0.98&maintenance=0.5` + ); + const data = await res.json(); -/** - * Executes npm commands that include user input safely - * @param `command` npm command to execute. - * @param `args` cli arguments - * @param `opts` cli flags - * @param `spawnOpts` options to pass to spawn command - * @returns Object with stdout, stderr, and exit code from command - */ -export async function npm( - command: NpmCommand, - args: string, - opts: NpmOptions = {}, - spawnOpts: SpawnOptionsWithoutStdio = {} -): Promise { - return new Promise((resolve, reject) => { - const cmdOptions = processOptions(opts); - const spawnArgs = [command, ...cmdOptions, args]; - log('npm %s', spawnArgs.join(' ')); - let stdout = ''; - let stderr = ''; - - const proc = spawn('npm', spawnArgs, { ...spawnOpts, shell: process.platform === 'win32' }); - - proc.stdout.on('data', (data) => { - stdout += data; - }); + log('Got %d result(s).', data.objects?.length ?? 0); - proc.stderr.on('data', (data) => { - stderr += data; - }); + return data.objects.map((result) => { + const { name, version, description = '', keywords = [], links = {} } = result.package; - proc.on('close', (code) => { - if (code > 0) { - reject({ stdout, stderr, code }); - } else { - resolve({ stdout, stderr, code }); - } + return { + id: name, + version, + description, + keywords, + url: links.npm ?? '', + } as ExtensionSearchResult; }); + } catch (err) { + log('%O', err); + + return []; + } +} + +export async function downloadPackage(name: string, versionOrTag: string, destination: string) { + const dLog = log.extend(name); + dLog('Starting download.'); + const res = await fetch(`https://registry.npmjs.org/${name}`); + const metadata = await res.json(); + const targetVersion = metadata['dist-tags'][versionOrTag] ?? versionOrTag; + + dLog('Resolved version %s to %s', versionOrTag, targetVersion); + + const tarballUrl = metadata.versions[targetVersion]?.dist.tarball; + + if (!tarballUrl) { + dLog('Unable to get tarball url.'); + throw new Error(`Could not find ${name}@${targetVersion} on npm.`); + } + + dLog('Fetching tarball.'); + const tarball = (await fetch(tarballUrl)).body; + // clean up previous version + // lgtm[js/path-injection] + await remove(destination); + // lgtm[js/path-injection] + await mkdir(destination); + + const extractor = tar.extract({ + strip: 1, + C: destination, + strict: true, }); + + dLog('Extracting tarball.'); + await streamPipeline(tarball, extractor); + dLog('Done downloading.'); } diff --git a/Composer/packages/server/src/server.ts b/Composer/packages/server/src/server.ts index 2d004377de..338d76e99d 100644 --- a/Composer/packages/server/src/server.ts +++ b/Composer/packages/server/src/server.ts @@ -47,9 +47,10 @@ export async function start(): Promise { ExtensionContext.useExpress(app); // load all installed plugins - setEnvDefault('COMPOSER_EXTENSION_DATA', path.resolve(__dirname, '../extensions.json')); + setEnvDefault('COMPOSER_EXTENSION_DATA', path.resolve(__dirname, '../../../.composer/extensions.json')); setEnvDefault('COMPOSER_BUILTIN_EXTENSIONS_DIR', path.resolve(__dirname, '../../../plugins')); - setEnvDefault('COMPOSER_REMOTE_EXTENSIONS_DIR', path.resolve(__dirname, '../../../.composer')); + // Composer/.composer/extensions + setEnvDefault('COMPOSER_REMOTE_EXTENSIONS_DIR', path.resolve(__dirname, '../../../.composer/extensions')); await ExtensionManager.loadAll(); const { login, authorize } = getAuthProvider(); diff --git a/Composer/packages/tools/language-servers/language-understanding/package.json b/Composer/packages/tools/language-servers/language-understanding/package.json index 6bb898d29c..2766d839d7 100644 --- a/Composer/packages/tools/language-servers/language-understanding/package.json +++ b/Composer/packages/tools/language-servers/language-understanding/package.json @@ -41,7 +41,6 @@ "ts-loader": "7.0.4", "ts-node": "^8.3.0", "typescript": "3.9.2", - "uglifyjs-webpack-plugin": "^2.2.0", "webpack": "^4.43.0", "webpack-cli": "^3.3.11", "webpack-dev-server": "^3.11.0", diff --git a/Composer/plugins/README.md b/Composer/plugins/README.md index 984a4f0438..ad6843a6f0 100644 --- a/Composer/plugins/README.md +++ b/Composer/plugins/README.md @@ -41,7 +41,7 @@ Plugin modules must come in one of the following forms: * A function called `initialize` is exported from the module Currently, plugins can be loaded into Composer using 1 of 2 methods: -* The plugin is placed in the /plugins/ folder, and contains a package.json file with `extendsComposer` set to `true` +* The plugin is placed in the /plugins/ folder * The plugin is loaded directly via changes to Composer code, using `ExtensionContext.loadPlugin(name, plugin)` The simplest form of a plugin module is below: diff --git a/Composer/plugins/authTest/package.json b/Composer/plugins/authTest/package.json index 8466fd38fa..6d0b2d1069 100644 --- a/Composer/plugins/authTest/package.json +++ b/Composer/plugins/authTest/package.json @@ -6,7 +6,9 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, - "extendsComposer": false, + "composer": { + "enabled": false + }, "author": "", "license": "ISC", "dependencies": { diff --git a/Composer/plugins/azurePublish/package.json b/Composer/plugins/azurePublish/package.json index f1d94745cf..65048396c0 100644 --- a/Composer/plugins/azurePublish/package.json +++ b/Composer/plugins/azurePublish/package.json @@ -7,7 +7,6 @@ "scripts": { "build": "tsc" }, - "extendsComposer": true, "dependencies": { "@azure/arm-appinsights": "2.1.0", "@azure/arm-appservice-profile-2019-03-01-hybrid": "1.0.0", diff --git a/Composer/plugins/githubAuth/package.json b/Composer/plugins/githubAuth/package.json index 9dc391c76a..5091c47863 100644 --- a/Composer/plugins/githubAuth/package.json +++ b/Composer/plugins/githubAuth/package.json @@ -6,7 +6,9 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, - "extendsComposer": false, + "composer": { + "enabled": false + }, "author": "", "license": "ISC", "dependencies": { diff --git a/Composer/plugins/localPublish/package.json b/Composer/plugins/localPublish/package.json index f33c54d4df..fb4ae25996 100644 --- a/Composer/plugins/localPublish/package.json +++ b/Composer/plugins/localPublish/package.json @@ -7,7 +7,6 @@ "test": "echo \"Error: no test specified\" && exit 1", "build": "tsc" }, - "extendsComposer": true, "author": "", "license": "ISC", "dependencies": { diff --git a/Composer/plugins/mockRemotePublish/package.json b/Composer/plugins/mockRemotePublish/package.json index 4273ac003b..ec65778363 100644 --- a/Composer/plugins/mockRemotePublish/package.json +++ b/Composer/plugins/mockRemotePublish/package.json @@ -7,7 +7,9 @@ "test": "echo \"Error: no test specified\" && exit 1", "build": "tsc" }, - "extendsComposer": false, + "composer": { + "enabled": false + }, "author": "", "license": "ISC", "dependencies": { diff --git a/Composer/plugins/mongoStorage/package.json b/Composer/plugins/mongoStorage/package.json index 2c764458ef..e0c30713b9 100644 --- a/Composer/plugins/mongoStorage/package.json +++ b/Composer/plugins/mongoStorage/package.json @@ -7,7 +7,9 @@ "test": "echo \"Error: no test specified\" && exit 1", "build": "tsc" }, - "extendsComposer": false, + "composer": { + "enabled": false + }, "author": "benbro@microsoft.com", "license": "MIT", "dependencies": { @@ -21,4 +23,4 @@ "resolutions": { "bl": "^2.2.1" } -} \ No newline at end of file +} diff --git a/Composer/plugins/publishTest/package.json b/Composer/plugins/publishTest/package.json index d628d2c15a..1cba407cf1 100644 --- a/Composer/plugins/publishTest/package.json +++ b/Composer/plugins/publishTest/package.json @@ -6,7 +6,9 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, - "extendsComposer": false, + "composer": { + "enabled": false + }, "author": "", "license": "ISC" -} \ No newline at end of file +} diff --git a/Composer/plugins/runtimes/package.json b/Composer/plugins/runtimes/package.json index 22e1ae4dab..20fac67d7d 100644 --- a/Composer/plugins/runtimes/package.json +++ b/Composer/plugins/runtimes/package.json @@ -7,7 +7,6 @@ "test": "echo \"Error: no test specified\" && exit 1", "build": "tsc" }, - "extendsComposer": true, "author": "", "license": "ISC", "dependencies": { diff --git a/Composer/plugins/samples/package.json b/Composer/plugins/samples/package.json index f56c51103e..5c92c49fa7 100644 --- a/Composer/plugins/samples/package.json +++ b/Composer/plugins/samples/package.json @@ -8,7 +8,6 @@ "build": "tsc", "start": "node lib/index.js" }, - "extendsComposer": true, "author": "", "license": "ISC", "dependencies": { diff --git a/Composer/plugins/webRoute/package.json b/Composer/plugins/webRoute/package.json index d3a58972b6..2f6bd98bc3 100644 --- a/Composer/plugins/webRoute/package.json +++ b/Composer/plugins/webRoute/package.json @@ -6,7 +6,9 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, - "extendsComposer": false, + "composer": { + "enabled": false + }, "author": "", "license": "ISC" } diff --git a/Composer/yarn.lock b/Composer/yarn.lock index bee4dd509f..aa62239508 100644 --- a/Composer/yarn.lock +++ b/Composer/yarn.lock @@ -3700,6 +3700,13 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/minipass@*": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/minipass/-/minipass-2.2.0.tgz#51ad404e8eb1fa961f75ec61205796807b6f9651" + integrity sha512-wuzZksN4w4kyfoOv/dlpov4NOunwutLA/q7uc00xU02ZyUY+aoM5PWIXEKBMnm0NHd4a+N71BMjq+x7+2Af1fg== + dependencies: + "@types/node" "*" + "@types/mock-fs@^3.6.30": version "3.6.30" resolved "https://registry.yarnpkg.com/@types/mock-fs/-/mock-fs-3.6.30.tgz#4d812541e87b23577261a5aa95f704dd3d01e410" @@ -3899,6 +3906,14 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370" integrity sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ== +"@types/tar@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/tar/-/tar-4.0.3.tgz#e2cce0b8ff4f285293243f5971bd7199176ac489" + integrity sha512-Z7AVMMlkI8NTWF0qGhC4QIX0zkV/+y0J8x7b/RsHrN0310+YNjoJd8UrApCiGBCWtKjxS9QhNqLi2UJNToh5hA== + dependencies: + "@types/minipass" "*" + "@types/node" "*" + "@types/testing-library__dom@*": version "7.0.2" resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-7.0.2.tgz#2906f8a0dce58b0746c6ab606f786bd06fe6940e" @@ -5514,8 +5529,8 @@ binary-extensions@^2.0.0: bl@^2.2.1, bl@^4.0.3: version "2.2.1" - resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/bl/-/bl-2.2.1.tgz#8c11a7b730655c5d56898cdc871224f40fd901d5" - integrity sha1-jBGntzBlXF1WiYzchxIk9A/ZAdU= + resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.1.tgz#8c11a7b730655c5d56898cdc871224f40fd901d5" + integrity sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g== dependencies: readable-stream "^2.3.5" safe-buffer "^5.1.1" @@ -5900,27 +5915,6 @@ bytes@3.1.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== -cacache@^12.0.2: - version "12.0.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390" - integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - cacache@^13.0.1: version "13.0.1" resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/cacache/-/cacache-13.0.1.tgz#a8000c21697089082f85287a1aec6e382024a71c" @@ -6276,6 +6270,11 @@ chownr@^1.1.2: resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" integrity sha1-Qtg31SOWiNVfMDADpQgjD6ZycUI= +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + chrome-trace-event@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" @@ -6683,7 +6682,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0, concat-stream@^1.5.2, concat-stream@^1.6.2: +concat-stream@^1.5.2, concat-stream@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -7392,11 +7391,6 @@ csstype@^2.5.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.3.tgz#b701e5968245bf9b08d54ac83d00b624e622a9fa" integrity sha512-rINUZXOkcBmoHWEyu7JdHu5JMzkGRoMX4ov9830WNgxf5UYxcBUO0QTKAqeJ5EZfSdlrcJYkC8WwfVW7JYi4yg== -cyclist@~0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" - integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= - cypress-plugin-tab@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/cypress-plugin-tab/-/cypress-plugin-tab-1.0.5.tgz#a40714148104004bb05ed62b1bf46bb544f8eb4a" @@ -8431,16 +8425,6 @@ duplexer@^0.1.1: resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= -duplexify@^3.4.2, duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -8612,7 +8596,7 @@ encoding@^0.1.11: dependencies: iconv-lite "~0.4.13" -end-of-stream@^1.0.0, end-of-stream@^1.1.0: +end-of-stream@^1.1.0: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== @@ -8645,7 +8629,7 @@ env-paths@^2.2.0: resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== -errno@^0.1.3, errno@~0.1.7: +errno@^0.1.3: version "0.1.7" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== @@ -9681,7 +9665,7 @@ find-cache-dir@^0.1.1: mkdirp "^0.5.1" pkg-dir "^1.0.0" -find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: +find-cache-dir@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== @@ -9785,14 +9769,6 @@ flatten@^1.0.2: resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" integrity sha1-2uRqnXj74lKSJYzB54CkHZXAN4I= -flush-write-stream@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" - integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== - dependencies: - inherits "^2.0.3" - readable-stream "^2.3.6" - fn-name@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7" @@ -9940,7 +9916,7 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -from2@^2.1.0, from2@^2.1.1: +from2@^2.1.1: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= @@ -10491,7 +10467,7 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== @@ -11123,7 +11099,7 @@ indexes-of@^1.0.1: resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= -infer-owner@^1.0.3, infer-owner@^1.0.4: +infer-owner@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== @@ -13457,21 +13433,13 @@ minizlib@^1.1.1: dependencies: minipass "^2.2.1" -mississippi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" - integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^3.0.0" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" + minipass "^3.0.0" + yallist "^4.0.0" mixin-deep@^1.2.0: version "1.3.2" @@ -13489,7 +13457,7 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.2, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@~0.5.1: +mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.2, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@^1.0.3, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -13717,6 +13685,11 @@ node-fetch@^2.1.2, node-fetch@^2.6.0, node-fetch@~2.6.0: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== +node-fetch@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + node-forge@^0.10.0: version "0.10.0" resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -14463,15 +14436,6 @@ pako@~1.0.5: resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732" integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw== -parallel-transform@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06" - integrity sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY= - dependencies: - cyclist "~0.2.2" - inherits "^2.0.3" - readable-stream "^2.1.5" - param-case@2.1.x, param-case@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" @@ -15730,14 +15694,6 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - pump@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" @@ -15746,15 +15702,6 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" @@ -16091,7 +16038,7 @@ read-text-file@^1.1.0: iconv-lite "^0.4.17" jschardet "^1.4.2" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -17008,7 +16955,7 @@ serialize-error@^5.0.0: dependencies: type-fest "^0.8.0" -serialize-javascript@^1.7.0, serialize-javascript@^2.1.2, serialize-javascript@^3.1.0: +serialize-javascript@^3.1.0, serialize-javascript@^4.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea" integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg== @@ -17502,13 +17449,6 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" -ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" - integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== - dependencies: - figgy-pudding "^3.5.1" - ssri@^7.0.0: version "7.1.0" resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/ssri/-/ssri-7.1.0.tgz#92c241bf6de82365b5c7fb4bd76e975522e1294d" @@ -17575,14 +17515,6 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" -stream-each@^1.1.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" - integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== - dependencies: - end-of-stream "^1.1.0" - stream-shift "^1.0.0" - stream-http@^2.7.2: version "2.8.3" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" @@ -17594,11 +17526,6 @@ stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" -stream-shift@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" - integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= - strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -18051,6 +17978,18 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.2" +tar@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" + integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + temp-file@^3.3.7: version "3.3.7" resolved "https://botbuilder.myget.org/F/botframework-cli/npm/temp-file/-/temp-file-3.3.7.tgz#686885d635f872748e384e871855958470aeb18a" @@ -18079,45 +18018,21 @@ terminal-link@^2.0.0: ansi-escapes "^4.2.1" supports-hyperlinks "^2.0.0" -terser-webpack-plugin@2.3.7: - version "2.3.7" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.7.tgz#4910ff5d1a872168cc7fa6cd3749e2b0d60a8a0b" - integrity sha512-xzYyaHUNhzgaAdBsXxk2Yvo/x1NJdslUaussK3fdpBbvttm1iIwU+c26dj9UxJcwk2c5UWt5F55MUTIA8BE7Dg== +terser-webpack-plugin@2.3.7, terser-webpack-plugin@^1.4.3, terser-webpack-plugin@^2.3.7: + version "2.3.8" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz#894764a19b0743f2f704e7c2a848c5283a696724" + integrity sha512-/fKw3R+hWyHfYx7Bv6oPqmk4HGQcrWLtV3X6ggvPuwPNHSnzvVV51z6OaaCOus4YLjutYGOz3pEpbhe6Up2s1w== dependencies: cacache "^13.0.1" find-cache-dir "^3.3.1" jest-worker "^25.4.0" p-limit "^2.3.0" schema-utils "^2.6.6" - serialize-javascript "^3.1.0" + serialize-javascript "^4.0.0" source-map "^0.6.1" terser "^4.6.12" webpack-sources "^1.4.3" -terser-webpack-plugin@^1.4.3: - version "1.4.3" - resolved "https://botbuilder.myget.org/F/botframework-cli/npm/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" - integrity sha1-Xsry29xfuZdF/QZ5H0b8ndscmnw= - dependencies: - cacache "^12.0.2" - find-cache-dir "^2.1.0" - is-wsl "^1.1.0" - schema-utils "^1.0.0" - serialize-javascript "^2.1.2" - source-map "^0.6.1" - terser "^4.1.2" - webpack-sources "^1.4.0" - worker-farm "^1.7.0" - -terser@^4.1.2: - version "4.3.9" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.9.tgz#e4be37f80553d02645668727777687dad26bbca8" - integrity sha512-NFGMpHjlzmyOtPL+fDw3G7+6Ueh/sz4mkaUYa4lJCxOPTNzd0Uj0aZJOmsDYoSQyfuVoWDMSWTPU3huyOm2zdA== - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - terser@^4.6.12: version "4.7.0" resolved "https://registry.yarnpkg.com/terser/-/terser-4.7.0.tgz#15852cf1a08e3256a80428e865a2fa893ffba006" @@ -18156,7 +18071,7 @@ throttleit@^1.0.0: resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" integrity sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw= -through2@^2.0.0, through2@^2.0.1: +through2@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== @@ -18563,29 +18478,6 @@ uglify-js@^3.5.1: commander "~2.20.3" source-map "~0.6.1" -uglify-js@^3.6.0: - version "3.7.3" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.7.3.tgz#f918fce9182f466d5140f24bb0ff35c2d32dcc6a" - integrity sha512-7tINm46/3puUA4hCkKYo4Xdts+JDaVC9ZPRcG8Xw9R4nhO/gZgUM3TENq8IF4Vatk8qCig4MzP/c8G4u2BkVQg== - dependencies: - commander "~2.20.3" - source-map "~0.6.1" - -uglifyjs-webpack-plugin@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-2.2.0.tgz#e75bc80e7f1937f725954c9b4c5a1e967ea9d0d7" - integrity sha512-mHSkufBmBuJ+KHQhv5H0MXijtsoA1lynJt1lXOaotja8/I0pR4L9oGaPIZw+bQBOFittXZg9OC1sXSGO9D9ZYg== - dependencies: - cacache "^12.0.2" - find-cache-dir "^2.1.0" - is-wsl "^1.1.0" - schema-utils "^1.0.0" - serialize-javascript "^1.7.0" - source-map "^0.6.1" - uglify-js "^3.6.0" - webpack-sources "^1.4.0" - worker-farm "^1.7.0" - uid-safe@~2.1.5: version "2.1.5" resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" @@ -19295,7 +19187,7 @@ webpack-sources@^1.1.0: source-list-map "^2.0.0" source-map "~0.6.1" -webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: +webpack-sources@^1.4.1, webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== @@ -19589,13 +19481,6 @@ workbox-window@^4.3.1: dependencies: workbox-core "^4.3.1" -worker-farm@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" - integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== - dependencies: - errno "~0.1.7" - worker-loader@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-2.0.0.tgz#45fda3ef76aca815771a89107399ee4119b430ac"