diff --git a/Composer/packages/client/src/store/index.tsx b/Composer/packages/client/src/store/index.tsx index b68af10d1c..53bf28c84f 100644 --- a/Composer/packages/client/src/store/index.tsx +++ b/Composer/packages/client/src/store/index.tsx @@ -5,9 +5,9 @@ import React, { useReducer, useRef } from 'react'; import once from 'lodash/once'; import { ImportResolverDelegate, LGParser } from 'botbuilder-lg'; import { LgFile, LuFile } from '@bfc/indexers'; +import { importResolverGenerator } from '@bfc/shared'; import { prepareAxios } from '../utils/auth'; -import { getFileName, getBaseName, getExtension } from '../utils/fileUtil'; import { reducer } from './reducer'; import bindActions from './action/bindActions'; @@ -121,17 +121,7 @@ export const StoreProvider: React.FC = props => { actions: boundActions, dispatch: interceptDispatch, resolvers: { - lgImportresolver: function(source: string, id: string) { - const locale = getExtension(source); - const targetFileName = getFileName(id); - let targetFileId = getBaseName(targetFileName); - if (locale) { - targetFileId += `.${locale}`; - } - const targetFile = getState().lgFiles.find(({ id }) => id === targetFileId); - if (!targetFile) throw new Error(`${id} lg file not found`); - return { id, content: targetFile.content }; - } as ImportResolverDelegate, + lgImportresolver: importResolverGenerator(getState().lgFiles, '.lg'), lgFileResolver: function(id: string) { const state = getState(); const { locale, lgFiles } = state; diff --git a/Composer/packages/client/src/store/reducer/index.ts b/Composer/packages/client/src/store/reducer/index.ts index 9ff35615a0..5c52853c00 100644 --- a/Composer/packages/client/src/store/reducer/index.ts +++ b/Composer/packages/client/src/store/reducer/index.ts @@ -4,14 +4,13 @@ import get from 'lodash/get'; import set from 'lodash/set'; import formatMessage from 'format-message'; -import { SensitiveProperties } from '@bfc/shared'; +import { SensitiveProperties, importResolverGenerator } from '@bfc/shared'; import { lgIndexer, luIndexer, LuFile, DialogInfo, dialogIndexer } from '@bfc/indexers'; -import { ImportResolverDelegate } from 'botbuilder-lg'; import { ActionTypes, FileTypes, BotStatus } from '../../constants'; import { DialogSetting, ReducerFunc } from '../types'; import { UserTokenPayload } from '../action/types'; -import { getExtension, getFileName, getBaseName } from '../../utils'; +import { getExtension } from '../../utils'; import settingStorage from '../../utils/dialogSettingStorage'; import luFileStatusStorage from '../../utils/luFileStatusStorage'; import { getReferredFiles } from '../../utils/luUtil'; @@ -146,18 +145,7 @@ const updateLgTemplate: ReducerFunc = (state, { id, content }) => { } return lgFile; }); - const lgImportresolver: ImportResolverDelegate = function(source: string, id: string) { - const locale = getExtension(source); - const targetFileName = getFileName(id); - let targetFileId = getBaseName(targetFileName); - if (locale) { - targetFileId += `.${locale}`; - } - const targetFile = lgFiles.find(({ id }) => id === targetFileId); - if (!targetFile) throw new Error(`file not found`); - return { id, content: targetFile.content }; - }; - + const lgImportresolver = importResolverGenerator(lgFiles, '.lg'); state.lgFiles = lgFiles.map(lgFile => { const { parse } = lgIndexer; const { id, content } = lgFile; diff --git a/Composer/packages/lib/shared/src/index.ts b/Composer/packages/lib/shared/src/index.ts index df4c90189c..fbf24c6f41 100644 --- a/Composer/packages/lib/shared/src/index.ts +++ b/Composer/packages/lib/shared/src/index.ts @@ -15,3 +15,4 @@ export * from './constant'; export * from './lgUtils'; export * from './walkerUtils'; export * from './copyUtils'; +export * from './resolverFactory'; diff --git a/Composer/packages/lib/shared/src/resolverFactory.ts b/Composer/packages/lib/shared/src/resolverFactory.ts new file mode 100644 index 0000000000..219fd54548 --- /dev/null +++ b/Composer/packages/lib/shared/src/resolverFactory.ts @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export declare type ResolverResource = { content: string; id: string }; +export declare type ImportResolverDelegate = (source: string, resourceId: string) => ResolverResource; + +function getFileName(path: string): string { + return path.split('/').pop() || path; +} + +/** + * + * @param resources resources feed to resolver + * @param ext resource extension, e.g. .lg, .lu + * @param defaultLocale complete resource id = [id].[locale][ext] + */ +export function importResolverGenerator( + resources: ResolverResource[], + ext = '', + defaultLocale = 'en-us' +): ImportResolverDelegate { + /** + * @param source current file id + * @param resourceId imported file id + * for example: + * in todosample.en-us.lg: + * [import](../common/common.lg) + * + * would resolve to common.en-us.lg || common.lg + * + * source = todosample || todosample.en-us || todosample.en-us.lg || todosample.lg + * resourceId = common || common.lg || ../common/common.lg + * + */ + return (source: string, resourceId: string) => { + // eslint-disable-next-line security/detect-non-literal-regexp + const extReg = new RegExp(ext + '$'); + const sourceId = getFileName(source).replace(extReg, ''); + const locale = sourceId.split('.').length > 1 ? sourceId.split('.').pop() : defaultLocale; + const targetId = getFileName(resourceId).replace(extReg, ''); + + const targetFile = + resources.find(({ id }) => id === `${targetId}.${locale}`) || resources.find(({ id }) => id === targetId); + + if (!targetFile) throw new Error(`file not found`); + return { + id: resourceId, + content: targetFile.content, + }; + }; +} diff --git a/Composer/packages/server/src/models/bot/botProject.ts b/Composer/packages/server/src/models/bot/botProject.ts index 6e1040e1d6..cc57d05afd 100644 --- a/Composer/packages/server/src/models/bot/botProject.ts +++ b/Composer/packages/server/src/models/bot/botProject.ts @@ -4,7 +4,7 @@ import fs from 'fs'; import has from 'lodash/has'; -import { getNewDesigner } from '@bfc/shared'; +import { getNewDesigner, importResolverGenerator } from '@bfc/shared'; import { FileInfo, DialogInfo, @@ -110,7 +110,7 @@ export class BotProject { this.files = await this._getFiles(); this.settings = await this.getEnvSettings('', false); this.dialogs = this.indexDialogs(); - this.lgFiles = lgIndexer.index(this.files, this._lgImportResolver); + this.lgFiles = lgIndexer.index(this.files, this._getLgImportResolver()); this.luFiles = luIndexer.index(this.files); await this._checkProjectStructure(); if (this.settings) { @@ -119,7 +119,7 @@ export class BotProject { }; public getIndexes = () => { - this.lgFiles = lgIndexer.index(this.files, this._lgImportResolver); + this.lgFiles = lgIndexer.index(this.files, this._getLgImportResolver()); return { botName: this.name, location: this.dir, @@ -545,30 +545,17 @@ export class BotProject { return dialogIndexer.index(this.files, this.name, this.getSchemas().sdk.content); } - /** - * @param source current file id - * @param id imported file path - * for example: - * in todosample.en-us.lg: - * [import](../common/common.lg) - * - * resolve to common.en-us.lg - * - * source = todosample.en-us || AddToDo - * id = ../common/common.lg || common.lg || common - */ - private _lgImportResolver = (source: string, id: string) => { - const locale = source.split('.').length > 1 ? source.split('.').pop() : ''; - let targetId = Path.basename(id, '.lg'); - if (locale) { - targetId += `.${locale}`; - } - const targetFile = this.lgFiles.find(({ id }) => id === targetId); - if (!targetFile) throw new Error('file not found'); - return { - id, - content: targetFile.content, - }; + private _getLgImportResolver = () => { + const lgFiles = this.files + .filter(({ name }) => name.endsWith('.lg')) + .map(({ name, content }) => { + return { + id: Path.basename(name, '.lg'), + content, + }; + }); + + return importResolverGenerator(lgFiles, '.lg', this.locale); }; // re index according to file change in a certain path @@ -580,7 +567,7 @@ export class BotProject { this.dialogs = this.indexDialogs(); break; case '.lg': - this.lgFiles = lgIndexer.index(this.files, this._lgImportResolver); + this.lgFiles = lgIndexer.index(this.files, this._getLgImportResolver()); break; case '.lu': this.luFiles = luIndexer.index(this.files); diff --git a/Composer/packages/server/src/services/project.ts b/Composer/packages/server/src/services/project.ts index c8f93657ee..0622108a65 100644 --- a/Composer/packages/server/src/services/project.ts +++ b/Composer/packages/server/src/services/project.ts @@ -3,7 +3,7 @@ import merge from 'lodash/merge'; import find from 'lodash/find'; -import { TextFile } from '@bfc/indexers'; +import { importResolverGenerator, ResolverResource } from '@bfc/shared'; import { BotProject } from '../models/bot/botProject'; import { LocationRef } from '../models/bot/interface'; @@ -33,38 +33,22 @@ export class BotProjectService { } } - public static lgImportResolver(source: string, id: string, projectId: string): TextFile { + public static lgImportResolver(source: string, id: string, projectId: string): ResolverResource { BotProjectService.initialize(); - let targetId = Path.basename(id, '.lg'); - if (targetId.lastIndexOf('.') === -1) { - const locale = source.lastIndexOf('.') > 0 ? source.split('.').pop() : 'en-us'; - targetId += `.${locale}`; - } - const targetFile = BotProjectService.currentBotProjects - .find(({ id }) => id === projectId) - ?.lgFiles.find(({ id }) => id === targetId); - if (!targetFile) throw new Error('lg file not found'); - return { - id, - content: targetFile.content, - }; + const project = BotProjectService.currentBotProjects.find(({ id }) => id === projectId); + if (!project) throw new Error('project not found'); + + const resolver = importResolverGenerator(project.lgFiles, '.lg'); + return resolver(source, id); } - public static luImportResolver(source: string, id: string, projectId: string): any { + public static luImportResolver(source: string, id: string, projectId: string): ResolverResource { BotProjectService.initialize(); - let targetId = Path.basename(id, '.lu'); - if (targetId.lastIndexOf('.') === -1) { - const locale = source.lastIndexOf('.') > 0 ? source.split('.').pop() : 'en-us'; - targetId += `.${locale}`; - } - const targetFile = BotProjectService.currentBotProjects - .find(({ id }) => id === projectId) - ?.luFiles.find(({ id }) => id === targetId); - if (!targetFile) throw new Error('lu file not found'); - return { - id, - content: targetFile.content, - }; + const project = BotProjectService.currentBotProjects.find(({ id }) => id === projectId); + if (!project) throw new Error('project not found'); + + const resolver = importResolverGenerator(project.luFiles, '.lu'); + return resolver(source, id); } public static staticMemoryResolver(projectId: string): string[] {