diff --git a/Composer/packages/client/package.json b/Composer/packages/client/package.json index 5c8da74fd9..842169d718 100644 --- a/Composer/packages/client/package.json +++ b/Composer/packages/client/package.json @@ -20,7 +20,7 @@ "@bfc/extensions": "*", "@bfc/indexers": "*", "@bfc/shared": "*", - "@bfcomposer/bf-lu": "^1.2.5", + "@bfcomposer/bf-lu": "^1.2.9", "@emotion/core": "^10.0.7", "@reach/router": "^1.2.1", "axios": "^0.18.0", diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/UserInput.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/UserInput.tsx index 97553ed0f0..d2550e546d 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/UserInput.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/UserInput.tsx @@ -8,6 +8,7 @@ import { FieldProps } from '@bfcomposer/react-jsonschema-form'; import formatMessage from 'format-message'; import { JSONSchema6 } from 'json-schema'; import { SDKTypes, MicrosoftInputDialog, ChoiceInput, ConfirmInput } from '@bfc/shared'; +import { DialogInfo } from '@bfc/indexers/lib/type'; import { TextWidget, SelectWidget } from '../../widgets'; import { LuEditorWidget } from '../../widgets/LuEditorWidget'; @@ -16,7 +17,6 @@ import { field } from './styles'; import { GetSchema, PromptFieldChangeHandler } from './types'; import { ChoiceInputSettings } from './ChoiceInput'; import { ConfirmInputSettings } from './ConfirmInput'; -import { DialogInfo } from '@bfc/indexers/lib/type'; const getOptions = (enumSchema: JSONSchema6) => { if (!enumSchema || !enumSchema.enum || !Array.isArray(enumSchema.enum)) { diff --git a/Composer/packages/extensions/obiformeditor/src/Form/widgets/LuEditorWidget.tsx b/Composer/packages/extensions/obiformeditor/src/Form/widgets/LuEditorWidget.tsx index c551e5bdab..dba358ad9f 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/widgets/LuEditorWidget.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/widgets/LuEditorWidget.tsx @@ -9,6 +9,7 @@ import { LuFile, filterSectionDiagnostics } from '@bfc/indexers'; import { LuIntentSection } from '@bfc/shared'; import { FormContext } from '../types'; + import { WidgetLabel } from './WidgetLabel'; interface LuEditorWidgetProps { diff --git a/Composer/packages/lib/indexers/package.json b/Composer/packages/lib/indexers/package.json index 461948af75..b0102f5096 100644 --- a/Composer/packages/lib/indexers/package.json +++ b/Composer/packages/lib/indexers/package.json @@ -29,7 +29,7 @@ }, "dependencies": { "@bfc/shared": "*", - "@bfcomposer/bf-lu": "^1.2.5", + "@bfcomposer/bf-lu": "^1.2.9", "botbuilder-lg": "^4.8.0-preview.106823", "botframework-expressions": "^4.8.0-preview.106476", "lodash": "^4.17.15" diff --git a/Composer/packages/lib/indexers/src/dialogIndexer.ts b/Composer/packages/lib/indexers/src/dialogIndexer.ts index eb36ee6ae6..fe89692f22 100644 --- a/Composer/packages/lib/indexers/src/dialogIndexer.ts +++ b/Composer/packages/lib/indexers/src/dialogIndexer.ts @@ -12,7 +12,7 @@ import { JsonWalk, VisitorFunc } from './utils/jsonWalk'; import { getBaseName } from './utils/help'; import { Diagnostic } from './diagnostic'; import ExtractMemoryPaths from './dialogUtils/extractMemoryPaths'; - +import ExtractIntentTriggers from './dialogUtils/extractIntentTriggers'; // find out all lg templates given dialog function ExtractLgTemplates(id, dialog): LgTemplateJsonPath[] { const templates: LgTemplateJsonPath[] = []; @@ -95,7 +95,7 @@ function ExtractLuIntents(dialog, id: string): ReferredLuIntents[] { // find out all triggers given dialog function ExtractTriggers(dialog): ITrigger[] { - const trigers: ITrigger[] = []; + const triggers: ITrigger[] = []; /** * * @param path , jsonPath string * @param value , current node value * @@ -118,7 +118,7 @@ function ExtractTriggers(dialog): ITrigger[] { } else if (trigger.isIntent && has(rule, 'intent')) { trigger.displayName = rule.intent; } - trigers.push(trigger); + triggers.push(trigger); } }); return true; @@ -126,7 +126,7 @@ function ExtractTriggers(dialog): ITrigger[] { return false; }; JsonWalk('$', dialog, visitor); - return trigers; + return triggers; } // find out all referred dialog @@ -204,6 +204,7 @@ function parse(id: string, content: any, schema: any) { luFile: getBaseName(luFile, '.lu'), lgFile: getBaseName(lgFile, '.lg'), triggers: ExtractTriggers(content), + intentTriggers: ExtractIntentTriggers(content), }; } diff --git a/Composer/packages/lib/indexers/src/dialogUtils/extractIntentTriggers.ts b/Composer/packages/lib/indexers/src/dialogUtils/extractIntentTriggers.ts new file mode 100644 index 0000000000..ff5e4bf01d --- /dev/null +++ b/Composer/packages/lib/indexers/src/dialogUtils/extractIntentTriggers.ts @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +import { SDKTypes } from '@bfc/shared'; + +import { VisitorFunc, JsonWalk } from '../utils/jsonWalk'; + +import { IIntentTrigger } from './types'; + +// find out all properties from given dialog +function ExtractIntentTriggers(value: any): IIntentTrigger[] { + const triggers: IIntentTrigger[] = []; + + const visitor: VisitorFunc = (path: string, value: any): boolean => { + if (value?.$type === SDKTypes.OnIntent) { + if (value.intent) { + const dialogs: string[] = []; + + const visitor: VisitorFunc = (path: string, value: any): boolean => { + if (value?.$type === SDKTypes.BeginDialog) { + if (value.dialog) { + dialogs.push(value.dialog); + } + return true; + } + return false; + }; + JsonWalk('$', value, visitor); + if (dialogs.length) { + triggers.push({ + intent: value.intent, + dialogs, + }); + } + } + return true; + } + return false; + }; + JsonWalk('$', value, visitor); + + return triggers; +} + +export default ExtractIntentTriggers; diff --git a/Composer/packages/lib/indexers/src/dialogUtils/types.ts b/Composer/packages/lib/indexers/src/dialogUtils/types.ts index 4e7960f655..974ccef47f 100644 --- a/Composer/packages/lib/indexers/src/dialogUtils/types.ts +++ b/Composer/packages/lib/indexers/src/dialogUtils/types.ts @@ -23,4 +23,9 @@ export interface IExpressionProperties { }; } +export interface IIntentTrigger { + intent: string; + dialogs: string[]; +} + export type CheckerFunc = (path: string, value: any, type: string, schema: any) => Diagnostic[] | null; // error msg diff --git a/Composer/packages/lib/indexers/src/luIndexer.ts b/Composer/packages/lib/indexers/src/luIndexer.ts index 164ea2d89c..2a17c1dae9 100644 --- a/Composer/packages/lib/indexers/src/luIndexer.ts +++ b/Composer/packages/lib/indexers/src/luIndexer.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { sectionHandler } from '@bfcomposer/bf-lu/lib/parser'; +import { sectionHandler } from '@bfcomposer/bf-lu/lib/parser/composerindex'; import get from 'lodash/get'; import { LuIntentSection } from '@bfc/shared'; diff --git a/Composer/packages/lib/indexers/src/type.ts b/Composer/packages/lib/indexers/src/type.ts index 5b47641a57..db7c525bc7 100644 --- a/Composer/packages/lib/indexers/src/type.ts +++ b/Composer/packages/lib/indexers/src/type.ts @@ -4,6 +4,7 @@ import { LuIntentSection } from '@bfc/shared'; import { Diagnostic } from './diagnostic'; +import { IIntentTrigger } from './dialogUtils/types'; export interface FileInfo { name: string; @@ -38,6 +39,7 @@ export interface DialogInfo { relativePath: string; userDefinedVariables: string[]; triggers: ITrigger[]; + intentTriggers: IIntentTrigger[]; } export interface LgTemplateJsonPath { diff --git a/Composer/packages/lib/indexers/src/types/bf-lu.d.ts b/Composer/packages/lib/indexers/src/types/bf-lu.d.ts index 2383096793..f8df414a4c 100644 --- a/Composer/packages/lib/indexers/src/types/bf-lu.d.ts +++ b/Composer/packages/lib/indexers/src/types/bf-lu.d.ts @@ -1,7 +1,7 @@ /// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -declare module '@bfcomposer/bf-lu/lib/parser' { +declare module '@bfcomposer/bf-lu/lib/parser/composerindex' { namespace parser { function parseFile(fileContent: any, log: any, locale: any): any; function validateLUISBlob(LUISJSONBlob: any): any; diff --git a/Composer/packages/lib/indexers/src/utils/luUtil.ts b/Composer/packages/lib/indexers/src/utils/luUtil.ts index 0baac955ff..c4127f8b88 100644 --- a/Composer/packages/lib/indexers/src/utils/luUtil.ts +++ b/Composer/packages/lib/indexers/src/utils/luUtil.ts @@ -7,7 +7,7 @@ * for more usage detail, please check client/__tests__/utils/luUtil.test.ts */ -import { sectionHandler } from '@bfcomposer/bf-lu/lib/parser'; +import { sectionHandler } from '@bfcomposer/bf-lu/lib/parser/composerindex'; import isEmpty from 'lodash/isEmpty'; import { LuIntentSection } from '@bfc/shared'; diff --git a/Composer/packages/server/__tests__/models/bot/luisPublisher.test.ts b/Composer/packages/server/__tests__/models/bot/luisPublisher.test.ts deleted file mode 100644 index 45c32fcefe..0000000000 --- a/Composer/packages/server/__tests__/models/bot/luisPublisher.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { FileUpdateType } from '../../../src/models/bot/interface'; -import { Path } from '../../../src/utility/path'; - -import { LuPublisher } from './../../../src/models/bot/luPublisher'; -import service from './../../../src/services/storage'; - -const botDir = Path.join(__dirname, '../../mocks/samplebots/bot1'); -const storage = service.getStorageClient('default'); - -describe('luis status management', () => { - it('will get luis status', async () => { - const luPublisher = new LuPublisher(botDir, storage); - const status = await luPublisher.loadStatus(['bot1/a.lu', 'bot1/b.lu', 'bot1/Main.lu']); - expect(status['bot1/a.lu'].lastUpdateTime).toBe(1); - expect(status['bot1/a.lu'].lastPublishTime).toBe(0); - }); - - it('can update luis status', async () => { - const luPublisher = new LuPublisher(botDir, storage); - - await luPublisher.loadStatus(['bot1/a.lu', 'bot1/b.lu', 'bot1/Main.lu']); - const oldUpdateTime = luPublisher.status['bot1/a.lu'].lastUpdateTime; - - await luPublisher.onFileChange('bot1/a.lu', FileUpdateType.UPDATE); - const newUpdateTime = luPublisher.status['bot1/a.lu'].lastUpdateTime; - // update should increase the update time - expect(newUpdateTime).toBeGreaterThan(oldUpdateTime); - }); -}); - -describe('get unpublishedFiles', () => { - it('will get unpublished files', async () => { - const lufiles = [ - { - diagnostics: [], - id: 'a', - relativePath: 'bot1/a.lu', - content: '', - intents: [], - }, - { - diagnostics: [], - id: 'b', - relativePath: 'bot1/b.lu', - content: '', - intents: [], - }, - ]; - - const luPublisher = new LuPublisher(botDir, storage); - await luPublisher.loadStatus(['bot1/a.lu', 'bot1/b.lu']); // relative path is key - - let files = luPublisher.getUnpublisedFiles(lufiles); - expect(files.length).toBe(2); - const curTime = Date.now(); - luPublisher.status['bot1/a.lu'].lastPublishTime = curTime; // assumming we publish a.lu - luPublisher.status['bot1/b.lu'].lastPublishTime = curTime; // and b.lu - files = luPublisher.getUnpublisedFiles(lufiles); - expect(files.length).toBe(0); - - await luPublisher.onFileChange('bot1/a.lu', FileUpdateType.UPDATE); - files = luPublisher.getUnpublisedFiles(lufiles); - expect(files.length).toBe(1); - }); -}); diff --git a/Composer/packages/server/package.json b/Composer/packages/server/package.json index 08e07b6842..aab1124761 100644 --- a/Composer/packages/server/package.json +++ b/Composer/packages/server/package.json @@ -57,7 +57,7 @@ "@bfc/lg-languageserver": "*", "@bfc/lu-languageserver": "*", "@bfc/shared": "*", - "@bfcomposer/bf-lu": "^1.2.5", + "@bfcomposer/bf-lu": "^1.2.9", "archiver": "^3.0.0", "axios": "^0.18.0", "azure-storage": "^2.10.3", diff --git a/Composer/packages/server/src/models/bot/botProject.ts b/Composer/packages/server/src/models/bot/botProject.ts index 22e86e5579..036b7b6d64 100644 --- a/Composer/packages/server/src/models/bot/botProject.ts +++ b/Composer/packages/server/src/models/bot/botProject.ts @@ -23,7 +23,7 @@ import { ISettingManager, OBFUSCATED_VALUE } from '../settings'; import log from '../../logger'; import { IFileStorage } from './../storage/interface'; -import { LocationRef, LuisStatus, FileUpdateType } from './interface'; +import { LocationRef } from './interface'; import { LuPublisher } from './luPublisher'; import { DialogSetting } from './interface'; @@ -89,7 +89,6 @@ export class BotProject { if (this.settings) { await this.luPublisher.setLuisConfig(this.settings.luis); } - await this.luPublisher.loadStatus(this.luFiles.map(f => f.relativePath)); }; public getIndexes = () => { @@ -99,7 +98,7 @@ export class BotProject { location: this.dir, dialogs: this.dialogs, lgFiles: this.lgFiles, - luFiles: this.mergeLuStatus(this.luFiles, this.luPublisher.status), + luFiles: this.luFiles, schemas: this.getSchemas(), botEnvironment: this.environment.getEnvironmentName(this.name), settings: this.settings, @@ -133,25 +132,6 @@ export class BotProject { await this.luPublisher.setLuisConfig(config.luis); }; - // merge the status managed by luPublisher to the LuFile structure to keep a - // unified interface - private mergeLuStatus = ( - luFiles: LuFile[], - luStatus: { - [key: string]: LuisStatus; - } - ) => { - return luFiles.map(x => { - if (!luStatus[x.relativePath]) { - throw new Error(`No luis status for lu file ${x.relativePath}`); - } - return { - ...x, - status: luStatus[x.relativePath], - }; - }); - }; - public getSchemas = () => { let editorSchema = this.defaultEditorSchema; let sdkSchema = this.defaultSDKSchema; @@ -305,9 +285,8 @@ export class BotProject { } await this._updateFile(luFile.relativePath, content); - await this.luPublisher.onFileChange(luFile.relativePath, FileUpdateType.UPDATE); - return this.mergeLuStatus(this.luFiles, this.luPublisher.status); + return this.luFiles; }; public createLuFile = async (id: string, content: string, dir: string = this.defaultDir(id)): Promise => { @@ -319,8 +298,7 @@ export class BotProject { // TODO: validate before save await this._createFile(relativePath, content); - await this.luPublisher.onFileChange(relativePath, FileUpdateType.CREATE); // let publisher know that some files changed - return this.mergeLuStatus(this.luFiles, this.luPublisher.status); // return a merged LUFile always + return this.luFiles; // return a merged LUFile always }; public removeLuFile = async (id: string): Promise => { @@ -331,41 +309,31 @@ export class BotProject { await this._removeFile(luFile.relativePath); - await this.luPublisher.onFileChange(luFile.relativePath, FileUpdateType.DELETE); await this._cleanUp(luFile.relativePath); - return this.mergeLuStatus(this.luFiles, this.luPublisher.status); + return this.luFiles; }; public publishLuis = async (authoringKey: string) => { this.luPublisher.setAuthoringKey(authoringKey); const referred = this.luFiles.filter(this.isReferred); - const unpublished = this.luPublisher.getUnpublisedFiles(referred); - const invalidLuFile = unpublished.filter(file => file.diagnostics.length !== 0); + const invalidLuFile = referred.filter(file => file.diagnostics.length !== 0); if (invalidLuFile.length !== 0) { const msg = this.generateErrorMessage(invalidLuFile); throw new Error(`The Following LuFile(s) are invalid: \n` + msg); } - const emptyLuFiles = unpublished.filter(this.isLuFileEmpty); + const emptyLuFiles = referred.filter(this.isLuFileEmpty); if (emptyLuFiles.length !== 0) { const msg = emptyLuFiles.map(file => file.id).join(' '); throw new Error(`You have the following empty LuFile(s): ` + msg); } - if (unpublished.length > 0) { - await this.luPublisher.publish(unpublished); + if (referred.length > 0) { + this.luPublisher.createCrossTrainConfig(this.dialogs, referred); + await this.luPublisher.publish(referred); } - return this.mergeLuStatus(this.luFiles, this.luPublisher.status); - }; - - public checkLuisPublished = () => { - const referredLuFiles = this.luFiles.filter(this.isReferred); - if (referredLuFiles.length <= 0) { - return true; - } else { - return this.luPublisher.checkLuisPublised(referredLuFiles); - } + return this.luFiles; }; public cloneFiles = async (locationRef: LocationRef): Promise => { @@ -522,7 +490,7 @@ export class BotProject { // load only from the data dir, otherwise may get "build" versions from // deployment process const root = this.dataDir; - const paths = await this.fileStorage.glob(pattern, root); + const paths = await this.fileStorage.glob([pattern, '!(generated/**)'], root); for (const filePath of paths.sort()) { const realFilePath: string = Path.join(root, filePath); diff --git a/Composer/packages/server/src/models/bot/interface.ts b/Composer/packages/server/src/models/bot/interface.ts index 92c75875fd..4cfa754c16 100644 --- a/Composer/packages/server/src/models/bot/interface.ts +++ b/Composer/packages/server/src/models/bot/interface.ts @@ -14,11 +14,6 @@ export interface ILuisSettings { }; } -export interface LuisStatus { - lastUpdateTime: number; - lastPublishTime: number; -} - // we will probably also use this interface to consolidate the processing of lu\lg\dialog export enum FileUpdateType { CREATE = 'create', @@ -40,8 +35,6 @@ export interface IOperationLUFile { relativePath?: string; content?: string; intents: []; - lastUpdateTime?: number; - lastPublishTime?: number; [key: string]: any; } diff --git a/Composer/packages/server/src/models/bot/luPublisher.ts b/Composer/packages/server/src/models/bot/luPublisher.ts index 12fcc28a09..67c589a260 100644 --- a/Composer/packages/server/src/models/bot/luPublisher.ts +++ b/Composer/packages/server/src/models/bot/luPublisher.ts @@ -2,30 +2,43 @@ // Licensed under the MIT License. import isEqual from 'lodash/isEqual'; -import { luBuild } from '@bfcomposer/bf-lu/lib/parser/lubuild'; -import { LuFile } from '@bfc/indexers'; +import keys from 'lodash/keys'; +import { LuFile, DialogInfo } from '@bfc/indexers'; import { Path } from './../../utility/path'; import { IFileStorage } from './../storage/interface'; -import { ILuisConfig, LuisStatus, FileUpdateType } from './interface'; +import { ILuisConfig } from './interface'; import log from './../../logger'; -const GENERATEDFOLDER = 'ComposerDialogs/generated'; -const LU_STATUS_FILE = 'luis.status.json'; -const DEFAULT_STATUS = { - lastUpdateTime: 1, - lastPublishTime: 0, // means unpublished -}; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const crossTrainer = require('@bfcomposer/bf-lu/lib/parser/cross-train/crossTrainer.js'); +// eslint-disable-next-line @typescript-eslint/no-var-requires +const luBuild = require('@bfcomposer/bf-lu/lib/parser/lubuild/builder.js'); + +const DIALOGS_FOLDER = 'ComposerDialogs'; +const GENERATEDFOLDER = 'generated'; +const INTERUPTION = 'interuption'; + +interface ICrossTrainConfig { + rootIds: string[]; + triggerRules: { [key: string]: any }; + intentName: string; + verbose: boolean; +} + export class LuPublisher { public botDir: string; + public dialogsDir: string; public generatedFolderPath: string; - public statusFile: string; + public interuptionFolderPath: string; public storage: IFileStorage; public config: ILuisConfig | null = null; - - // key: filePath relative to bot dir - // value: lastUpdateTime && lastPublishTime - public status: { [key: string]: LuisStatus } = {}; + public crossTrainMapRule: ICrossTrainConfig = { + rootIds: [], + triggerRules: {}, + intentName: '_Interruption', + verbose: true, + }; private builder = new luBuild.Builder(message => { log(message); @@ -33,159 +46,221 @@ export class LuPublisher { constructor(path: string, storage: IFileStorage) { this.botDir = path; - this.generatedFolderPath = Path.join(this.botDir, GENERATEDFOLDER); - this.statusFile = Path.join(this.generatedFolderPath, LU_STATUS_FILE); + this.dialogsDir = Path.join(this.botDir, DIALOGS_FOLDER); + this.generatedFolderPath = Path.join(this.dialogsDir, GENERATEDFOLDER); + this.interuptionFolderPath = Path.join(this.generatedFolderPath, INTERUPTION); this.storage = storage; } - // load luis status from luis.status.json - public loadStatus = async (files: string[] = []) => { - if (await this.storage.exists(this.statusFile)) { - const content = await this.storage.readFile(this.statusFile); - this.status = JSON.parse(content); - } + public publish = async (luFiles: LuFile[]) => { + try { + await this._createGeneratedDir(); - // make sure all LU file have an initial value - files.forEach(f => { - if (!this.status[f]) { - this.status[f] = { ...DEFAULT_STATUS }; // use ... ensure don't referred to the same object - } - }); - return this.status; - }; + //do cross train before publish + await this._crossTrain(luFiles); + + await this._runBuild(luFiles); - // reset status when config changed, because status don't represent the current config - public resetStatus = () => { - for (const key in this.status) { - this.status[key] = { ...DEFAULT_STATUS }; + //remove the cross train result + await this._cleanCrossTrain(); + } catch (error) { + throw new Error(error.message ?? error.text ?? 'Error publishing to LUIS.'); } }; - public saveStatus = async () => { - if (!(await this.storage.exists(this.generatedFolderPath))) { - await this.storage.mkDir(this.generatedFolderPath); + public getLuisConfig = () => this.config; + + public setLuisConfig = async (config: ILuisConfig) => { + if (!isEqual(config, this.config)) { + this.config = config; + await this._deleteDir(this.generatedFolderPath); } - await this.storage.writeFile(this.statusFile, JSON.stringify(this.status, null, 2)); }; - public onFileChange = async (relativePath: string, type: FileUpdateType) => { - switch (type) { - case FileUpdateType.CREATE: - this.status[relativePath] = { - lastUpdateTime: Date.now(), - lastPublishTime: 0, // unpublished - }; - break; - case FileUpdateType.UPDATE: - this.status[relativePath].lastUpdateTime = Date.now(); - break; - case FileUpdateType.DELETE: - delete this.status[relativePath]; - break; + public setAuthoringKey = (key: string) => { + if (this.config) { + this.config.authoringKey = key; } - await this.saveStatus(); }; - public publish = async (luFiles: LuFile[]) => { - if (!luFiles.length) { - throw new Error('No luis file exist'); - } - const config = this._getConfig(); - const curTime = Date.now(); - try { - const loadResult = await this._loadLuConatents(luFiles); - const buildResult = await this.builder.build( - loadResult.luContents, - loadResult.recognizers, - config.authoringKey, - config.region, - config.botName, - config.suffix, - config.fallbackLocal, - false, - loadResult.multiRecognizers, - loadResult.settings - ); - - // update pubish status after sucessfully published - luFiles.forEach(f => { - this.status[f.relativePath].lastPublishTime = curTime; - }); - await this.saveStatus(); - await this.builder.writeDialogAssets(buildResult, true, this.generatedFolderPath); - } catch (error) { - throw new Error(error.body?.error?.message || error.message || 'Error publishing to LUIS.'); + //generate the cross-train config + /* the config is like + { + rootIds: [ + 'main.lu', + 'main.fr-fr.lu' + ], + triggerRules: { + 'main.lu': { + 'dia1.lu': 'dia1_trigger', + 'dia2.lu': 'dia2_trigger' + }, + 'dia2.lu': { + 'dia3.lu': 'dia3_trigger', + 'dia4.lu': 'dia4_trigger' + }, + 'main.fr-fr.lu': { + 'dia1.fr-fr.lu': 'dia1_trigger' + } + }, + intentName: '_Interruption', + verbose: true } - }; + */ + public createCrossTrainConfig = (dialogs: DialogInfo[], luFiles: LuFile[]) => { + const triggerRules = {}; + const countMap = {}; - public getUnpublisedFiles = (files: LuFile[]) => { - // unpublished means either - // 1. there is no status tracking - // 2. the status shows that lastPublishTime < lastUpdateTime - return files.filter(f => { - return ( - !this.status[f.relativePath] || - this.status[f.relativePath].lastPublishTime <= this.status[f.relativePath].lastUpdateTime - ); + //map all referred lu files + luFiles.forEach(file => { + countMap[file.id] = 0; }); - }; - public checkLuisPublised = (files: LuFile[]) => { - const unpublished = this.getUnpublisedFiles(files); - return unpublished.length === 0; + dialogs.forEach(dialog => { + const { intentTriggers } = dialog; + const fileId = this._createConfigId(dialog.id); + if (intentTriggers.length) { + //find the trigger's dialog that use a recognizer + intentTriggers.forEach(item => { + const used = item.dialogs.filter(dialog => { + if (typeof countMap[dialog] === 'number') { + countMap[dialog]++; + return true; + } + return false; + }); + if (used.length) { + const result = used.reduce((result, temp) => { + const id = this._createConfigId(temp); + result[id] = item.intent; + return result; + }, {}); + triggerRules[fileId] = { ...triggerRules[fileId], ...result }; + } + }); + } + }); + + this.crossTrainMapRule.rootIds = keys(countMap) + .filter(key => (countMap[key] === 0 || key === 'Main') && triggerRules[this._createConfigId(key)]) + .map(item => this._createConfigId(item)); + this.crossTrainMapRule.triggerRules = triggerRules; }; - public getLuisConfig = () => this.config; + private _createConfigId(fileId) { + return `${fileId}.lu`; + } - public setLuisConfig = async (config: ILuisConfig) => { - if (!isEqual(config, this.config)) { - this.config = config; - await this._deleteGenerated(this.generatedFolderPath); - this.resetStatus(); + private async _createGeneratedDir() { + if (!(await this.storage.exists(this.generatedFolderPath))) { + await this.storage.mkDir(this.generatedFolderPath); } - }; + } - public setAuthoringKey = (key: string) => { - if (this.config) { - this.config.authoringKey = key; + private _needCrossTrain() { + return !!this.crossTrainMapRule.rootIds.length; + } + + private async _crossTrain(luFiles: LuFile[]) { + if (!this._needCrossTrain()) return; + const luContents = luFiles.map(file => { + return { content: file.content, id: this._createConfigId(file.id) }; + }); + + const result = await crossTrainer.crossTrain(luContents, [], this.crossTrainMapRule); + + await this._writeFiles(result.luResult); + } + + private async _writeFiles(crossTrainResult) { + if (!(await this.storage.exists(this.interuptionFolderPath))) { + await this.storage.mkDir(this.interuptionFolderPath); } - }; + for (const key of crossTrainResult.keys()) { + const fileName = Path.basename(key); + const newFileId = Path.join(this.interuptionFolderPath, fileName); + await this.storage.writeFile(newFileId, crossTrainResult.get(key).Content); + } + } + + private async _runBuild(luFiles: LuFile[]) { + const config = await this._getConfig(luFiles); + if (config.models.length === 0) { + throw new Error('No luis file exist'); + } + const loadResult = await this._loadLuConatents(config.models); + const buildResult = await this.builder.build( + loadResult.luContents, + loadResult.recognizers, + config.authoringKey, + config.region, + config.botName, + config.suffix, + config.fallbackLocal, + false, + loadResult.multiRecognizers, + loadResult.settings + ); + await this.builder.writeDialogAssets(buildResult, true, this.generatedFolderPath); + } + //delete files in generated folder - private async _deleteGenerated(path: string) { + private async _deleteDir(path: string) { if (await this.storage.exists(path)) { const files = await this.storage.readDir(path); for (const file of files) { const curPath = Path.join(path, file); if ((await this.storage.stat(curPath)).isDir) { - await this._deleteGenerated(curPath); + await this._deleteDir(curPath); } else { await this.storage.removeFile(curPath); } } + await this.storage.rmDir(path); } } - private _getConfig = () => { - const luConfig = { - authoringKey: this.config?.authoringKey || '', - region: this.config?.authoringRegion || '', - botName: this.config?.name || '', - suffix: this.config?.environment || '', - fallbackLocal: this.config?.defaultLanguage || 'en-us', + private _getConfig = async (luFiles: LuFile[]) => { + if (!this.config) { + throw new Error('Please complete your Luis settings'); + } + + const luConfig: any = { + authoringKey: this.config.authoringKey || '', + region: this.config.authoringRegion || '', + botName: this.config.name || '', + suffix: this.config.environment || '', + fallbackLocal: this.config.defaultLanguage || 'en-us', }; - return luConfig; - }; - private _loadLuConatents = async (luFiles: LuFile[]) => { - const pathList = luFiles.map(file => { - return Path.resolve(this.botDir, file.relativePath); + luConfig.models = []; + //add all lu file after cross train + let paths: string[] = []; + if (this._needCrossTrain()) { + paths = await this.storage.glob('**/*.lu', this.interuptionFolderPath); + luConfig.models = paths.map(filePath => Path.join(this.interuptionFolderPath, filePath)); + } + + //add the lu file that are not in interuption folder. + luFiles.forEach(file => { + if (!~paths.indexOf(`${file.id}.lu`)) { + luConfig.models.push(Path.resolve(this.botDir, file.relativePath)); + } }); + return luConfig; + }; + private _loadLuConatents = async (paths: string[]) => { return await this.builder.loadContents( - pathList, + paths, this.config?.defaultLanguage || '', this.config?.environment || '', this.config?.authoringRegion || '' ); }; + + private async _cleanCrossTrain() { + if (!this._needCrossTrain()) return; + await this._deleteDir(this.interuptionFolderPath); + } } diff --git a/Composer/packages/server/src/models/storage/interface.ts b/Composer/packages/server/src/models/storage/interface.ts index bfefa3b4cc..6c5077b589 100644 --- a/Composer/packages/server/src/models/storage/interface.ts +++ b/Composer/packages/server/src/models/storage/interface.ts @@ -28,7 +28,7 @@ export interface IFileStorage { removeFile(path: string): Promise; mkDir(path: string, options?: MakeDirectoryOptions): Promise; rmDir(path: string): Promise; - glob(pattern: string, path: string): Promise; + glob(pattern: string | string[], path: string): Promise; copyFile(src: string, dest: string): Promise; rename(oldPath: string, newPath: string): Promise; } diff --git a/Composer/packages/server/src/models/storage/localDiskStorage.ts b/Composer/packages/server/src/models/storage/localDiskStorage.ts index 72bc116d0e..9213486128 100644 --- a/Composer/packages/server/src/models/storage/localDiskStorage.ts +++ b/Composer/packages/server/src/models/storage/localDiskStorage.ts @@ -63,7 +63,7 @@ export class LocalDiskStorage implements IFileStorage { await rmDir(path); } - async glob(pattern: string, path: string): Promise { + async glob(pattern: string | string[], path: string): Promise { return await glob(pattern, { cwd: path, dot: true }); } diff --git a/Composer/packages/server/src/types/bf-lu.d.ts b/Composer/packages/server/src/types/bf-lu.d.ts deleted file mode 100644 index 1eff54c908..0000000000 --- a/Composer/packages/server/src/types/bf-lu.d.ts +++ /dev/null @@ -1,35 +0,0 @@ -/// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -declare module '@bfcomposer/bf-lu/lib/parser/lubuild' { - namespace luBuild { - class Builder { - private readonly handler: (input: string) => any; - - constructor(handler: any); - - loadContents( - files: string[], - culture: string, - suffix: string, - region: string, - stdin?: string - ): { luContents: any[]; recognizers: Map; multiRecognizers: Map; settings: any }; - - build( - luContents: any[], - recognizers: Map, - authoringKey: string, - region: string, - botName: string, - suffix: string, - fallbackLocale: string, - deleteOldVersion: boolean, - multiRecognizers: Map, - settings: any - ): any[]; - - writeDialogAssets(contents: any[], force: boolean, out: string): void; - } - } -} diff --git a/Composer/packages/tools/language-servers/language-understanding/package.json b/Composer/packages/tools/language-servers/language-understanding/package.json index 02bc7a28fa..f74756c548 100644 --- a/Composer/packages/tools/language-servers/language-understanding/package.json +++ b/Composer/packages/tools/language-servers/language-understanding/package.json @@ -18,8 +18,8 @@ "start:server": "cd demo && ts-node ./src/server.ts" }, "dependencies": { - "@microsoft/bf-cli-command": "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@microsoft/bf-cli-command/-/@microsoft/bf-cli-command-1.0.0.tgz", - "@bfcomposer/bf-lu": "^1.2.5", + "@microsoft/bf-cli-command": "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@microsoft/bf-cli-command/-/@microsoft/bf-cli-command-1.0.1.tgz", + "@bfcomposer/bf-lu": "^1.2.9", "@types/node": "^12.0.4", "express": "^4.15.2", "monaco-editor-core": "^0.17.0", diff --git a/Composer/yarn.lock b/Composer/yarn.lock index 3f746b86fe..262a651ef3 100644 --- a/Composer/yarn.lock +++ b/Composer/yarn.lock @@ -2120,17 +2120,13 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@bfcomposer/bf-lu@^1.2.5": - version "1.2.5" - resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@bfcomposer/bf-lu/-/@bfcomposer/bf-lu-1.2.5.tgz#4d3707746518d013e19aa6f26467c94342874459" - integrity sha1-TTcHdGUY0BPhmqbyZGfJQ0KHRFk= +"@bfcomposer/bf-lu@^1.2.9": + version "1.2.9" + resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@bfcomposer/bf-lu/-/@bfcomposer/bf-lu-1.2.9.tgz#b2fec0d8f1c3d4d0e526a5bdbf9e5c9b3ab5b71d" + integrity sha1-sv7A2PHD1NDlJqW9v55cmzq1tx0= dependencies: "@azure/cognitiveservices-luis-authoring" "3.0.1" "@azure/ms-rest-azure-js" "2.0.1" - "@microsoft/bf-cli-command" "1.0.0" - "@oclif/command" "~1.5.19" - "@oclif/config" "~1.13.3" - "@oclif/errors" "~1.2.2" antlr4 "^4.7.2" chalk "2.4.1" console-stream "^0.1.1" @@ -2138,6 +2134,7 @@ delay "^4.3.0" fs-extra "^8.1.0" get-stdin "^6.0.0" + globby "^10.0.1" intercept-stdout "^0.1.2" lodash "^4.17.15" node-fetch "^2.1.2" @@ -2724,19 +2721,19 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" -"@microsoft/bf-cli-command@1.0.0", "@microsoft/bf-cli-command@https://botbuilder.myget.org/F/botbuilder-declarative/npm/@microsoft/bf-cli-command/-/@microsoft/bf-cli-command-1.0.0.tgz": - version "1.0.0" - resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@microsoft/bf-cli-command/-/@microsoft/bf-cli-command-1.0.0.tgz#0d681e93690c4e872bf1181a3ff02f094830929e" - integrity sha1-DWgek2kMTocr8RgaP/AvCUgwkp4= +"@microsoft/bf-cli-command@https://botbuilder.myget.org/F/botbuilder-declarative/npm/@microsoft/bf-cli-command/-/@microsoft/bf-cli-command-1.0.1.tgz": + version "1.0.1" + resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@microsoft/bf-cli-command/-/@microsoft/bf-cli-command-1.0.1.tgz#5c7f650015f839d80a7739bc70f8cd5fa90a1e48" dependencies: - "@oclif/command" "~1.5.13" - "@oclif/config" "~1.12.12" + "@oclif/command" "~1.5.19" + "@oclif/config" "~1.13.3" "@oclif/errors" "~1.2.2" applicationinsights "^1.0.8" chalk "2.4.1" cli-ux "~4.9.3" debug "^4.1.1" fs-extra "^7.0.1" + tslib "~1.10.0" "@microsoft/load-themed-styles@^1.7.13": version "1.9.5" @@ -2756,15 +2753,36 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@nodelib/fs.scandir@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== + dependencies: + "@nodelib/fs.stat" "2.0.3" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== + "@nodelib/fs.stat@^1.1.2": version "1.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== -"@oclif/command@^1.5.13", "@oclif/command@~1.5.13", "@oclif/command@~1.5.19": +"@nodelib/fs.walk@^1.2.3": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + dependencies: + "@nodelib/fs.scandir" "2.1.3" + fastq "^1.6.0" + +"@oclif/command@^1.5.13", "@oclif/command@~1.5.19": version "1.5.19" - resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@oclif/command/-/@oclif/command-1.5.19.tgz#13f472450eb83bd6c6871a164c03eadb5e1a07ed" - integrity sha1-E/RyRQ64O9bGhxoWTAPq214aB+0= + resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.5.19.tgz#13f472450eb83bd6c6871a164c03eadb5e1a07ed" + integrity sha512-6+iaCMh/JXJaB2QWikqvGE9//wLEVYYwZd5sud8aLoLKog1Q75naZh2vlGVtg5Mq/NqpqGQvdIjJb3Bm+64AUQ== dependencies: "@oclif/config" "^1" "@oclif/errors" "^1.2.2" @@ -2782,14 +2800,6 @@ debug "^4.1.1" tslib "^1.9.3" -"@oclif/config@~1.12.12": - version "1.12.12" - resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@oclif/config/-/@oclif/config-1.12.12.tgz#09e6628fe454214bbd7630ae18d2bee0db6bd533" - integrity sha1-CeZij+RUIUu9djCuGNK+4Ntr1TM= - dependencies: - debug "^4.1.1" - tslib "^1.9.3" - "@oclif/errors@^1.2.2", "@oclif/errors@~1.2.2": version "1.2.2" resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@oclif/errors/-/@oclif/errors-1.2.2.tgz#9d8f269b15f13d70aa93316fed7bebc24688edc2" @@ -4272,6 +4282,11 @@ array-union@^1.0.1, array-union@^1.0.2: dependencies: array-uniq "^1.0.1" +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -7395,6 +7410,13 @@ dir-glob@^2.2.1: dependencies: path-type "^3.0.0" +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" @@ -8335,6 +8357,18 @@ fast-glob@^2.0.2, fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" +fast-glob@^3.0.3: + version "3.2.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.2.tgz#ade1a9d91148965d4bf7c51f72e1ca662d32e63d" + integrity sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" @@ -8345,6 +8379,13 @@ fast-levenshtein@~2.0.4, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fastq@^1.6.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.1.tgz#4570c74f2ded173e71cf0beb08ac70bb85826791" + integrity sha512-mpIH5sKYueh3YyeJwqtVo8sORi0CgtmkVbK6kZStpQlZBYQuTzG2CZ7idSiJuA7bY0SFCWUc5WIs+oYumGCQNw== + dependencies: + reusify "^1.0.4" + faye-websocket@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" @@ -8944,7 +8985,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0: +glob-parent@^5.0.0, glob-parent@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== @@ -9091,6 +9132,20 @@ globby@8.0.2: pify "^3.0.0" slash "^1.0.0" +globby@^10.0.1: + version "10.0.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" + integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== + dependencies: + "@types/glob" "^7.1.1" + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.0.3" + glob "^7.1.3" + ignore "^5.1.1" + merge2 "^1.2.3" + slash "^3.0.0" + globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -9647,6 +9702,11 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +ignore@^5.1.1: + version "5.1.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" + integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== + immer@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" @@ -11817,6 +11877,11 @@ merge2@^1.2.3: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5" integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA== +merge2@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" + integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -11846,7 +11911,7 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.0: +micromatch@^4.0.0, micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== @@ -13178,6 +13243,11 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + pbkdf2@^3.0.3: version "3.0.17" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" @@ -13209,6 +13279,11 @@ picomatch@^2.0.5: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== +picomatch@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" + integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== + pify@^2.0.0, pify@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -14984,6 +15059,11 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -15033,6 +15113,11 @@ run-node@^1.0.0: resolved "https://registry.yarnpkg.com/run-node/-/run-node-1.0.0.tgz#46b50b946a2aa2d4947ae1d886e9856fd9cabe5e" integrity sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A== +run-parallel@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" @@ -15397,6 +15482,11 @@ slash@^2.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" @@ -16506,7 +16596,7 @@ tsconfig-paths@^3.4.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@^1.10.0, tslib@^1.9.2, tslib@^1.9.3: +tslib@^1.10.0, tslib@^1.9.2, tslib@^1.9.3, tslib@~1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==