fix (all): path and fs normalization.

This commit is contained in:
Rodrigo Rodriguez 2024-09-10 23:25:07 -03:00
parent 49deb3e487
commit 31ea62d526
59 changed files with 209 additions and 120 deletions

View file

@ -173,21 +173,7 @@ export class GBAdminService implements IGBAdminService {
const gbaiPath = GBUtil.getGBAIPath(min.instance.botId, packageType, null); const gbaiPath = GBUtil.getGBAIPath(min.instance.botId, packageType, null);
const localFolder = path.join('work', gbaiPath); const localFolder = path.join('work', gbaiPath);
// .gbot packages are handled using storage API, so no download await deployer['deployPackage2'](min, user, localFolder, true);
// of local resources is required.
const gbai = GBUtil.getGBAIPath(min.instance.botId);
if (packageType === 'gbkb') {
await deployer['cleanupPackage'](min.instance, packageName);
}
if (!GBConfigService.get('STORAGE_NAME')) {
const filePath = path.join(GBConfigService.get('STORAGE_LIBRARY'), gbaiPath);
GBUtil.copyIfNewerRecursive(filePath, localFolder);
} else {
await deployer['downloadFolder'](min, path.join('work', `${gbai}`), path.basename(localFolder));
}
await deployer['deployPackage2'](min, user, localFolder);
} }
public static async rebuildIndexPackageCommand(min: GBMinInstance, deployer: GBDeployer) { public static async rebuildIndexPackageCommand(min: GBMinInstance, deployer: GBDeployer) {

View file

@ -1500,7 +1500,7 @@ export class DialogKeywords {
'cache', 'cache',
`${fileOnly.replace(/\s/gi, '')}-${GBAdminService.getNumberIdentifier()}.${ext}` `${fileOnly.replace(/\s/gi, '')}-${GBAdminService.getNumberIdentifier()}.${ext}`
); );
fs.writeFile(localName1, buf, { encoding: null }); await fs.writeFile(localName1, buf, { encoding: null });
url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', path.basename(localName1)); url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', path.basename(localName1));
} }
@ -1513,7 +1513,7 @@ export class DialogKeywords {
const buf = await fs.readFile(filename); const buf = await fs.readFile(filename);
const gbaiName = GBUtil.getGBAIPath(min.botId); const gbaiName = GBUtil.getGBAIPath(min.botId);
const localName = path.join('work', gbaiName, 'cache', `tmp${GBAdminService.getRndReadableIdentifier()}.${ext}`); const localName = path.join('work', gbaiName, 'cache', `tmp${GBAdminService.getRndReadableIdentifier()}.${ext}`);
fs.writeFile(localName, buf, { encoding: null }); await fs.writeFile(localName, buf, { encoding: null });
url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', path.basename(localName)); url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', path.basename(localName));
} }
@ -1546,7 +1546,7 @@ export class DialogKeywords {
const gbaiName = GBUtil.getGBAIPath(min.botId); const gbaiName = GBUtil.getGBAIPath(min.botId);
const localName = path.join('work', gbaiName, 'cache', `qr${GBAdminService.getRndReadableIdentifier()}.png`); const localName = path.join('work', gbaiName, 'cache', `qr${GBAdminService.getRndReadableIdentifier()}.png`);
fs.writeFile(localName, buf, { encoding: null }); await fs.writeFile(localName, buf, { encoding: null });
const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', path.basename(localName)); const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', path.basename(localName));
return { data: data, localName: localName, url: url }; return { data: data, localName: localName, url: url };

View file

@ -155,7 +155,7 @@ export class GBVMService extends GBService {
// Write VBS file without pragma keywords. // Write VBS file without pragma keywords.
fs.writeFile(urlJoin(folder, vbsFile), text); await fs.writeFile(urlJoin(folder, vbsFile), text);
} }
// Process node_modules install. // Process node_modules install.
@ -215,7 +215,7 @@ export class GBVMService extends GBService {
"async-retry": "1.3.3" "async-retry": "1.3.3"
} }
}`; }`;
fs.writeFile(urlJoin(folder, 'package.json'), packageJson); await fs.writeFile(urlJoin(folder, 'package.json'), packageJson);
GBLogEx.info(min, `Installing .gbdialog node_modules for ${min.botId}...`); GBLogEx.info(min, `Installing .gbdialog node_modules for ${min.botId}...`);
const npmPath = urlJoin(process.env.PWD, 'node_modules', '.bin', 'npm'); const npmPath = urlJoin(process.env.PWD, 'node_modules', '.bin', 'npm');
@ -498,10 +498,10 @@ export class GBVMService extends GBService {
// Generates function JSON metadata to be used later. // Generates function JSON metadata to be used later.
const jsonFile = `${filename}.json`; const jsonFile = `${filename}.json`;
fs.writeFile(jsonFile, JSON.stringify(metadata)); await fs.writeFile(jsonFile, JSON.stringify(metadata));
const mapFile = `${filename}.map`; const mapFile = `${filename}.map`;
fs.writeFile(mapFile, JSON.stringify(map)); await fs.writeFile(mapFile, JSON.stringify(map));
// Execute off-line code tasks // Execute off-line code tasks
@ -711,7 +711,7 @@ export class GBVMService extends GBService {
code = ji.default(code, ' '); code = ji.default(code, ' ');
fs.writeFile(jsfile, code); await fs.writeFile(jsfile, code);
GBLogEx.info(min, `[GBVMService] Finished loading of ${filename}, JavaScript from Word: \n ${code}`); GBLogEx.info(min, `[GBVMService] Finished loading of ${filename}, JavaScript from Word: \n ${code}`);
} }
@ -723,7 +723,7 @@ export class GBVMService extends GBService {
// Creates an empty object that will receive Sequelize fields. // Creates an empty object that will receive Sequelize fields.
const tablesFile = `${task.file}.tables.json`; const tablesFile = `${task.file}.tables.json`;
fs.writeFile(tablesFile, JSON.stringify(task.tables)); await fs.writeFile(tablesFile, JSON.stringify(task.tables));
} }
} }
} }

View file

@ -414,7 +414,7 @@ export class SystemKeywords {
const buffer = pngPages[0].content; const buffer = pngPages[0].content;
const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', path.basename(localName)); const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', path.basename(localName));
fs.writeFile(localName, buffer, { encoding: null }); await fs.writeFile(localName, buffer, { encoding: null });
return { localName: localName, url: url, data: buffer }; return { localName: localName, url: url, data: buffer };
} }
@ -707,7 +707,7 @@ export class SystemKeywords {
// Writes it to disk and calculate hash. // Writes it to disk and calculate hash.
const data = await response.arrayBuffer(); const data = await response.arrayBuffer();
fs.writeFile(localName, Buffer.from(data), { encoding: null }); await fs.writeFile(localName, Buffer.from(data), { encoding: null });
const hash = new Uint8Array(md5.array(data)); const hash = new Uint8Array(md5.array(data));
// Performs uploading passing local hash. // Performs uploading passing local hash.
@ -1114,7 +1114,7 @@ export class SystemKeywords {
const localName = path.join('work', gbaiName, 'cache', `csv${GBAdminService.getRndReadableIdentifier()}.csv`); const localName = path.join('work', gbaiName, 'cache', `csv${GBAdminService.getRndReadableIdentifier()}.csv`);
const url = file['@microsoft.graph.downloadUrl']; const url = file['@microsoft.graph.downloadUrl'];
const response = await fetch(url); const response = await fetch(url);
fs.writeFile(localName, Buffer.from(await response.arrayBuffer()), { encoding: null }); await fs.writeFile(localName, Buffer.from(await response.arrayBuffer()), { encoding: null });
var workbook = new Excel.Workbook(); var workbook = new Excel.Workbook();
let worksheet = await workbook.csv.readFile(localName); let worksheet = await workbook.csv.readFile(localName);
@ -1490,7 +1490,7 @@ export class SystemKeywords {
user.userSystemId, user.userSystemId,
'systemPrompt.txt' 'systemPrompt.txt'
); );
fs.writeFile(systemPromptFile, text); await fs.writeFile(systemPromptFile, text);
} }
} }
@ -1981,7 +1981,7 @@ export class SystemKeywords {
const res = await fetch(url); const res = await fetch(url);
let buf: any = Buffer.from(await res.arrayBuffer()); let buf: any = Buffer.from(await res.arrayBuffer());
localName = path.join('work', gbaiName, 'cache', `tmp${GBAdminService.getRndReadableIdentifier()}.docx`); localName = path.join('work', gbaiName, 'cache', `tmp${GBAdminService.getRndReadableIdentifier()}.docx`);
fs.writeFile(localName, buf, { encoding: null }); await fs.writeFile(localName, buf, { encoding: null });
// Replace image path on all elements of data. // Replace image path on all elements of data.
@ -2019,7 +2019,7 @@ export class SystemKeywords {
); );
const response = await fetch(url); const response = await fetch(url);
const buf = Buffer.from(await response.arrayBuffer()); const buf = Buffer.from(await response.arrayBuffer());
fs.writeFile(imageName, buf, { encoding: null }); await fs.writeFile(imageName, buf, { encoding: null });
const getNormalSize = ({ width, height, orientation }) => { const getNormalSize = ({ width, height, orientation }) => {
return (orientation || 0) >= 5 ? [height, width] : [width, height]; return (orientation || 0) >= 5 ? [height, width] : [width, height];
@ -2068,7 +2068,7 @@ export class SystemKeywords {
doc.setData(data).render(); doc.setData(data).render();
buf = doc.getZip().generate({ type: 'nodebuffer', compression: 'DEFLATE' }); buf = doc.getZip().generate({ type: 'nodebuffer', compression: 'DEFLATE' });
fs.writeFile(localName, buf, { encoding: null }); await fs.writeFile(localName, buf, { encoding: null });
return { localName: localName, url: url, data: buf }; return { localName: localName, url: url, data: buf };
} }
@ -2526,7 +2526,7 @@ export class SystemKeywords {
const buf = Buffer.from(data.Payment.QrCodeBase64Image, 'base64'); const buf = Buffer.from(data.Payment.QrCodeBase64Image, 'base64');
const localName = path.join('work', gbaiName, 'cache', `qr${GBAdminService.getRndReadableIdentifier()}.png`); const localName = path.join('work', gbaiName, 'cache', `qr${GBAdminService.getRndReadableIdentifier()}.png`);
fs.writeFile(localName, buf, { encoding: null }); await fs.writeFile(localName, buf, { encoding: null });
const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', path.basename(localName)); const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', path.basename(localName));
GBLogEx.info(min, `GBPay: ${data.MerchantOrderId} OK: ${url}.`); GBLogEx.info(min, `GBPay: ${data.MerchantOrderId} OK: ${url}.`);
@ -2752,7 +2752,7 @@ export class SystemKeywords {
// Criação de um arquivo temporário para enviar // Criação de um arquivo temporário para enviar
const tempFilePath = path.resolve('temp_image.jpg'); const tempFilePath = path.resolve('temp_image.jpg');
fs.writeFile(tempFilePath, imageBuffer); await fs.writeFile(tempFilePath, imageBuffer);
// Publicação da imagem // Publicação da imagem
const page = new Page(pageId); const page = new Page(pageId);

View file

@ -100,6 +100,7 @@ export class GBConfigService {
case 'CLOUD_USERNAME': case 'CLOUD_USERNAME':
value = undefined; value = undefined;
break; break;
case 'CLOUD_PASSWORD': case 'CLOUD_PASSWORD':
value = undefined; value = undefined;
break; break;

View file

@ -452,7 +452,7 @@ export class GBConversationalService {
const waveFilename = `work/tmp${name}.pcm`; const waveFilename = `work/tmp${name}.pcm`;
let audio = await textToSpeech.repairWavHeaderStream(res.result as any); let audio = await textToSpeech.repairWavHeaderStream(res.result as any);
fs.writeFile(waveFilename, audio); await fs.writeFile(waveFilename, audio);
const oggFilenameOnly = `tmp${name}.ogg`; const oggFilenameOnly = `tmp${name}.ogg`;
const oggFilename = `work/${oggFilenameOnly}`; const oggFilename = `work/${oggFilenameOnly}`;
@ -482,7 +482,7 @@ export class GBConversationalService {
const dest = `work/tmp${name}.wav`; const dest = `work/tmp${name}.wav`;
const src = `work/tmp${name}.ogg`; const src = `work/tmp${name}.ogg`;
fs.writeFile(src, oggFile.read()); await fs.writeFile(src, oggFile.read());
const makeMp3 = shell([ const makeMp3 = shell([
'node_modules/ffmpeg-static/ffmpeg', // TODO: .exe on MSWin. 'node_modules/ffmpeg-static/ffmpeg', // TODO: .exe on MSWin.

View file

@ -135,7 +135,7 @@ export class GBCoreService implements IGBCoreService {
} else if (this.dialect === 'sqlite') { } else if (this.dialect === 'sqlite') {
storage = GBConfigService.get('STORAGE_FILE'); storage = GBConfigService.get('STORAGE_FILE');
if (!await GBUtil.exists(storage)) { if (!(await GBUtil.exists(storage))) {
process.env.STORAGE_SYNC = 'true'; process.env.STORAGE_SYNC = 'true';
} }
} else { } else {
@ -313,7 +313,7 @@ STORAGE_SYNC_ALTER=true
ENDPOINT_UPDATE=true ENDPOINT_UPDATE=true
`; `;
fs.writeFile('.env', env); await fs.writeFile('.env', env);
} }
/** /**
@ -323,7 +323,10 @@ ENDPOINT_UPDATE=true
*/ */
public async ensureProxy(port): Promise<string> { public async ensureProxy(port): Promise<string> {
try { try {
if (await GBUtil.exists('node_modules/ngrok/bin/ngrok.exe') || await GBUtil.exists('node_modules/.bin/ngrok')) { if (
(await GBUtil.exists('node_modules/ngrok/bin/ngrok.exe')) ||
(await GBUtil.exists('node_modules/.bin/ngrok'))
) {
return await ngrok.connect({ port: port }); return await ngrok.connect({ port: port });
} else { } else {
GBLog.warn('ngrok executable not found. Check installation or node_modules folder.'); GBLog.warn('ngrok executable not found. Check installation or node_modules folder.');
@ -825,15 +828,15 @@ ENDPOINT_UPDATE=true
public async ensureFolders(instances, deployer: GBDeployer) { public async ensureFolders(instances, deployer: GBDeployer) {
let libraryPath = GBConfigService.get('STORAGE_LIBRARY'); let libraryPath = GBConfigService.get('STORAGE_LIBRARY');
if (!await GBUtil.exists(libraryPath)) { if (!(await GBUtil.exists(libraryPath))) {
mkdirp.sync(libraryPath); mkdirp.sync(libraryPath);
} }
await this.syncBotStorage(instances, 'default', deployer, libraryPath); await this.syncBotStorage(instances, 'default', deployer, libraryPath);
const files = fs.readdir(libraryPath); const files = await fs.readdir(libraryPath);
await CollectionUtil.asyncForEach(files, async file => { await CollectionUtil.asyncForEach(files, async file => {
if (file.trim().toLowerCase() !== 'default.gbai') { if (file.trim().toLowerCase() !== 'default.gbai' && file.charAt(0) !== '_') {
let botId = file.replace(/\.gbai/, ''); let botId = file.replace(/\.gbai/, '');
await this.syncBotStorage(instances, botId, deployer, libraryPath); await this.syncBotStorage(instances, botId, deployer, libraryPath);
@ -855,7 +858,7 @@ ENDPOINT_UPDATE=true
instance = await deployer.deployBlankBot(botId, mobile, email); instance = await deployer.deployBlankBot(botId, mobile, email);
const gbaiPath = path.join(libraryPath, `${botId}.gbai`); const gbaiPath = path.join(libraryPath, `${botId}.gbai`);
if (!await GBUtil.exists(gbaiPath)) { if (!(await GBUtil.exists(gbaiPath))) {
fs.mkdir(gbaiPath, { recursive: true }); fs.mkdir(gbaiPath, { recursive: true });
const base = path.join(process.env.PWD, 'templates', 'default.gbai'); const base = path.join(process.env.PWD, 'templates', 'default.gbai');

View file

@ -152,9 +152,7 @@ export class GBDeployer implements IGBDeployer {
const isDirectory = async source => (await fs.lstat(source)).isDirectory(); const isDirectory = async source => (await fs.lstat(source)).isDirectory();
const getDirectories = async source => const getDirectories = async source =>
(await fs.readdir(source)) (await fs.readdir(source)).map(name => path.join(source, name)).filter(isDirectory);
.map(name => path.join(source, name))
.filter(isDirectory);
const dirs = await getDirectories(directory); const dirs = await getDirectories(directory);
await CollectionUtil.asyncForEach(dirs, async element => { await CollectionUtil.asyncForEach(dirs, async element => {
// For each folder, checks its extensions looking for valid packages. // For each folder, checks its extensions looking for valid packages.
@ -472,7 +470,7 @@ export class GBDeployer implements IGBDeployer {
} }
}); });
GBLogEx.info(min, `Processing ${rows.length} rows from ${filePath}...`); GBLogEx.info(min, `Processing ${rows.length} rows from ${path.basename(filePath)}...`);
rows = null; rows = null;
return obj; return obj;
} }
@ -497,13 +495,13 @@ export class GBDeployer implements IGBDeployer {
// Creates each subfolder. // Creates each subfolder.
let pathBase = localPath; let pathBase = localPath;
if (!await GBUtil.exists(pathBase)) { if (!(await GBUtil.exists(pathBase))) {
fs.mkdir(pathBase); fs.mkdir(pathBase);
} }
await CollectionUtil.asyncForEach(parts, async item => { await CollectionUtil.asyncForEach(parts, async item => {
pathBase = packagePath.join(pathBase, item); pathBase = packagePath.join(pathBase, item);
if (!await GBUtil.exists(pathBase)) { if (!(await GBUtil.exists(pathBase))) {
fs.mkdir(pathBase); fs.mkdir(pathBase);
} }
}); });
@ -514,7 +512,7 @@ export class GBDeployer implements IGBDeployer {
packagePath = urlJoin(packagePath, remotePath); packagePath = urlJoin(packagePath, remotePath);
let url = `${baseUrl}/drive/root:/${packagePath}:/children`; let url = `${baseUrl}/drive/root:/${packagePath}:/children`;
GBLogEx.info(min, `Download URL: ${url}`); GBLogEx.info(min, `Downloading: ${url}`);
const res = await client.api(url).get(); const res = await client.api(url).get();
const documents = res.value; const documents = res.value;
@ -529,7 +527,7 @@ export class GBDeployer implements IGBDeployer {
const itemPath = packagePath.join(localPath, remotePath, item.name); const itemPath = packagePath.join(localPath, remotePath, item.name);
if (item.folder) { if (item.folder) {
if (!await GBUtil.exists(itemPath)) { if (!(await GBUtil.exists(itemPath))) {
fs.mkdir(itemPath); fs.mkdir(itemPath);
} }
const nextFolder = urlJoin(remotePath, item.name); const nextFolder = urlJoin(remotePath, item.name);
@ -549,10 +547,10 @@ export class GBDeployer implements IGBDeployer {
const url = item['@microsoft.graph.downloadUrl']; const url = item['@microsoft.graph.downloadUrl'];
const response = await fetch(url); const response = await fetch(url);
fs.writeFile(itemPath, Buffer.from(await response.arrayBuffer()), { encoding: null }); await fs.writeFile(itemPath, Buffer.from(await response.arrayBuffer()), { encoding: null });
fs.utimes(itemPath, new Date(), new Date(item.lastModifiedDateTime)); fs.utimes(itemPath, new Date(), new Date(item.lastModifiedDateTime));
} else { } else {
GBLogEx.info(min, `Local is up to date: ${itemPath}...`); GBLogEx.info(min, `Local is up to date: ${path.basename(itemPath)}...`);
} }
} }
}); });
@ -594,11 +592,27 @@ export class GBDeployer implements IGBDeployer {
/** /**
* Deploys a folder into the bot storage. * Deploys a folder into the bot storage.
*/ */
public async deployPackage2(min: GBMinInstance, user, localPath: string) { public async deployPackage2(min: GBMinInstance, user, packageWorkFolder: string, download = false) {
const packageType = path.extname(localPath); const packageName = path.basename(packageWorkFolder);
const packageType = path.extname(packageWorkFolder);
let handled = false; let handled = false;
let pck = null; let pck = null;
const gbai = GBUtil.getGBAIPath(min.instance.botId);
if (download) {
if (packageType === 'gbkb') {
await this.cleanupPackage(min.instance, packageName);
}
if (!GBConfigService.get('STORAGE_NAME')) {
const filePath = path.join(GBConfigService.get('STORAGE_LIBRARY'), gbai);
GBUtil.copyIfNewerRecursive(filePath, packageWorkFolder);
} else {
await this.downloadFolder(min, path.join('work', `${gbai}`), packageName);
}
}
// Asks for each .gbapp if it will handle the package publishing. // Asks for each .gbapp if it will handle the package publishing.
const _this = this; const _this = this;
@ -608,7 +622,7 @@ export class GBDeployer implements IGBDeployer {
if ( if (
(pck = await e.onExchangeData(min, 'handlePackage', { (pck = await e.onExchangeData(min, 'handlePackage', {
name: localPath, name: packageWorkFolder,
createPackage: async packageName => { createPackage: async packageName => {
return await _this.deployPackageToStorage(min.instance.instanceId, packageName); return await _this.deployPackageToStorage(min.instance.instanceId, packageName);
}, },
@ -634,7 +648,7 @@ export class GBDeployer implements IGBDeployer {
case '.gbot': case '.gbot':
// Extracts configuration information from .gbot files. // Extracts configuration information from .gbot files.
min.instance.params = await this.loadParamsFromTabular(min, localPath); min.instance.params = await this.loadParamsFromTabular(min, packageWorkFolder);
if (min.instance.params) { if (min.instance.params) {
let connections = []; let connections = [];
@ -667,12 +681,11 @@ export class GBDeployer implements IGBDeployer {
const packagePath = GBUtil.getGBAIPath(min.botId, null); const packagePath = GBUtil.getGBAIPath(min.botId, null);
const localFolder = path.join('work', packagePath, 'connections.json'); const localFolder = path.join('work', packagePath, 'connections.json');
fs.writeFile(localFolder, JSON.stringify(connections), { encoding: null }); await fs.writeFile(localFolder, JSON.stringify(connections), { encoding: null });
// Updates instance object. // Updates instance object.
await this.core.saveInstance(min.instance); await this.core.saveInstance(min.instance);
GBLogEx.info(min, `Reloading bot ${min.botId}...`);
GBServer.globals.minService.unmountBot(min.botId); GBServer.globals.minService.unmountBot(min.botId);
GBServer.globals.minService.mountBot(min.instance); GBServer.globals.minService.mountBot(min.instance);
GBLogEx.info(min, `Bot ${min.botId} reloaded.`); GBLogEx.info(min, `Bot ${min.botId} reloaded.`);
@ -683,7 +696,7 @@ export class GBDeployer implements IGBDeployer {
// Deploys .gbkb into the storage. // Deploys .gbkb into the storage.
const service = new KBService(this.core.sequelize); const service = new KBService(this.core.sequelize);
await service.deployKb(this.core, this, localPath, min); await service.deployKb(this.core, this, packageWorkFolder, min);
break; break;
case '.gbdialog': case '.gbdialog':
@ -691,15 +704,14 @@ export class GBDeployer implements IGBDeployer {
// it to the VM. // it to the VM.
const vm = new GBVMService(); const vm = new GBVMService();
await vm.loadDialogPackage(localPath, min, this.core, this); await vm.loadDialogPackage(packageWorkFolder, min, this.core, this);
GBLogEx.verbose(min, `Dialogs (.gbdialog) for ${min.botId} loaded.`); GBLogEx.verbose(min, `Dialogs (.gbdialog) for ${min.botId} loaded.`);
break; break;
case '.gbtheme': case '.gbtheme':
// Updates server listeners to serve theme files in .gbtheme. // Updates server listeners to serve theme files in .gbtheme.
const packageName = path.basename(localPath); GBServer.globals.server.use(`/themes/${packageName}`, express.static(packageWorkFolder));
GBServer.globals.server.use(`/themes/${packageName}`, express.static(localPath));
GBLogEx.verbose(min, `Theme (.gbtheme) assets accessible at: /themes/${packageName}.`); GBLogEx.verbose(min, `Theme (.gbtheme) assets accessible at: /themes/${packageName}.`);
break; break;
@ -707,13 +719,13 @@ export class GBDeployer implements IGBDeployer {
case '.gbapp': case '.gbapp':
// Dynamically compiles and loads .gbapp packages (Node.js packages). // Dynamically compiles and loads .gbapp packages (Node.js packages).
await this.callGBAppCompiler(localPath, this.core); await this.callGBAppCompiler(packageWorkFolder, this.core);
break; break;
case '.gblib': case '.gblib':
// Dynamically compiles and loads .gblib packages (Node.js packages). // Dynamically compiles and loads .gblib packages (Node.js packages).
await this.callGBAppCompiler(localPath, this.core); await this.callGBAppCompiler(packageWorkFolder, this.core);
break; break;
default: default:
@ -736,7 +748,6 @@ export class GBDeployer implements IGBDeployer {
public async undeployPackageFromPackageName(instance: IGBInstance, packageName: string) { public async undeployPackageFromPackageName(instance: IGBInstance, packageName: string) {
// Gets information about the package. // Gets information about the package.
const p = await this.getStoragePackageByName(instance.instanceId, packageName); const p = await this.getStoragePackageByName(instance.instanceId, packageName);
const packagePath = GBUtil.getGBAIPath(instance.botId, null, packageName); const packagePath = GBUtil.getGBAIPath(instance.botId, null, packageName);
@ -881,7 +892,7 @@ export class GBDeployer implements IGBDeployer {
if (!(await GBUtil.exists(`${root}/build`)) && process.env.DISABLE_WEB !== 'true') { if (!(await GBUtil.exists(`${root}/build`)) && process.env.DISABLE_WEB !== 'true') {
// Write a .env required to fix some bungs in create-react-app tool. // Write a .env required to fix some bungs in create-react-app tool.
fs.writeFile(`${root}/.env`, 'SKIP_PREFLIGHT_CHECK=true'); await fs.writeFile(`${root}/.env`, 'SKIP_PREFLIGHT_CHECK=true');
// Install modules and compiles the web app. // Install modules and compiles the web app.
@ -931,7 +942,6 @@ export class GBDeployer implements IGBDeployer {
); );
GBServer.globals.server.use(`/${botId}/cache`, express.static(urlJoin('work', gbaiName, 'cache'))); GBServer.globals.server.use(`/${botId}/cache`, express.static(urlJoin('work', gbaiName, 'cache')));
// FEAT-A7B1F6 // FEAT-A7B1F6
GBServer.globals.server.use( GBServer.globals.server.use(
@ -956,8 +966,8 @@ export class GBDeployer implements IGBDeployer {
GBLogEx.info(0, `Deploying General Bots Application (.gbapp) or Library (.gblib): ${path.basename(gbappPath)}...`); GBLogEx.info(0, `Deploying General Bots Application (.gbapp) or Library (.gblib): ${path.basename(gbappPath)}...`);
let folder = path.join(gbappPath, 'node_modules'); let folder = path.join(gbappPath, 'node_modules');
if (process.env.GBAPP_DISABLE_COMPILE !== 'true') { if (process.env.GBAPP_DISABLE_COMPILE !== 'true') {
if (!await GBUtil.exists(folder)) { if (!(await GBUtil.exists(folder))) {
GBLogEx.info(0, `Installing modules for ${gbappPath}...`); GBLogEx.info(0, `Installing modules for ${path.basename(gbappPath)}...`);
child_process.execSync('npm install', { cwd: gbappPath }); child_process.execSync('npm install', { cwd: gbappPath });
} }
} }
@ -967,7 +977,7 @@ export class GBDeployer implements IGBDeployer {
// Runs TSC in .gbapp folder. // Runs TSC in .gbapp folder.
if (process.env.GBAPP_DISABLE_COMPILE !== 'true') { if (process.env.GBAPP_DISABLE_COMPILE !== 'true') {
GBLogEx.info(0, `Compiling: ${gbappPath}.`); GBLogEx.info(0, `Compiling: ${path.basename(gbappPath)}.`);
child_process.execSync(path.join(process.env.PWD, 'node_modules/.bin/tsc'), { cwd: gbappPath }); child_process.execSync(path.join(process.env.PWD, 'node_modules/.bin/tsc'), { cwd: gbappPath });
} }

View file

@ -36,6 +36,10 @@
import { createRpcServer } from '@push-rpc/core'; import { createRpcServer } from '@push-rpc/core';
import AuthenticationContext from 'adal-node'; import AuthenticationContext from 'adal-node';
import arrayBufferToBuffer from 'arraybuffer-to-buffer'; import arrayBufferToBuffer from 'arraybuffer-to-buffer';
import { watch } from 'fs';
import debounce from 'lodash.debounce';
import chokidar from 'chokidar';
import { import {
AutoSaveStateMiddleware, AutoSaveStateMiddleware,
BotFrameworkAdapter, BotFrameworkAdapter,
@ -45,7 +49,14 @@ import {
UserState UserState
} from 'botbuilder'; } from 'botbuilder';
import { FacebookAdapter } from 'botbuilder-adapter-facebook'; import { FacebookAdapter } from 'botbuilder-adapter-facebook';
import { AttachmentPrompt, ConfirmPrompt, DialogSet, OAuthPrompt, TextPrompt, WaterfallDialog } from 'botbuilder-dialogs'; import {
AttachmentPrompt,
ConfirmPrompt,
DialogSet,
OAuthPrompt,
TextPrompt,
WaterfallDialog
} from 'botbuilder-dialogs';
import { MicrosoftAppCredentials } from 'botframework-connector'; import { MicrosoftAppCredentials } from 'botframework-connector';
import { import {
GBDialogStep, GBDialogStep,
@ -313,39 +324,48 @@ export class GBMinService {
let dir = `work/${gbai}/cache`; let dir = `work/${gbai}/cache`;
const botId = gbai.replace(/\.[^/.]+$/, ''); const botId = gbai.replace(/\.[^/.]+$/, '');
if (!await GBUtil.exists(dir)) { if (!(await GBUtil.exists(dir))) {
mkdirp.sync(dir); mkdirp.sync(dir);
} }
dir = `work/${gbai}/profile`; dir = `work/${gbai}/profile`;
if (!await GBUtil.exists(dir)) { if (!(await GBUtil.exists(dir))) {
mkdirp.sync(dir); mkdirp.sync(dir);
} }
dir = `work/${gbai}/uploads`; dir = `work/${gbai}/uploads`;
if (!await GBUtil.exists(dir)) { if (!(await GBUtil.exists(dir))) {
mkdirp.sync(dir); mkdirp.sync(dir);
} }
dir = `work/${gbai}/${botId}.gbkb`; dir = `work/${gbai}/${botId}.gbkb`;
if (!await GBUtil.exists(dir)) { if (!(await GBUtil.exists(dir))) {
mkdirp.sync(dir); mkdirp.sync(dir);
} }
await this.watchPackages(min, 'gbkb');
dir = `work/${gbai}/${botId}.gbkb/docs-vectorized`; dir = `work/${gbai}/${botId}.gbkb/docs-vectorized`;
if (!await GBUtil.exists(dir)) { if (!(await GBUtil.exists(dir))) {
mkdirp.sync(dir); mkdirp.sync(dir);
} }
dir = `work/${gbai}/${botId}.gbdialog`; dir = `work/${gbai}/${botId}.gbdialog`;
if (!await GBUtil.exists(dir)) { if (!(await GBUtil.exists(dir))) {
mkdirp.sync(dir); mkdirp.sync(dir);
} }
await this.watchPackages(min, 'gbdialog');
dir = `work/${gbai}/${botId}.gbot`; dir = `work/${gbai}/${botId}.gbot`;
if (!await GBUtil.exists(dir)) { if (!(await GBUtil.exists(dir))) {
mkdirp.sync(dir); mkdirp.sync(dir);
} }
await this.watchPackages(min, 'gbot');
dir = `work/${gbai}/${botId}.gbui`; dir = `work/${gbai}/${botId}.gbui`;
if (!await GBUtil.exists(dir)) { if (!(await GBUtil.exists(dir))) {
mkdirp.sync(dir); mkdirp.sync(dir);
} }
dir = `work/${gbai}/users`; dir = `work/${gbai}/users`;
if (!await GBUtil.exists(dir)) { if (!(await GBUtil.exists(dir))) {
mkdirp.sync(dir); mkdirp.sync(dir);
} }
@ -387,10 +407,9 @@ export class GBMinService {
const manifest = `${instance.botId}-Teams.zip`; const manifest = `${instance.botId}-Teams.zip`;
const packageTeams = urlJoin(`work`, GBUtil.getGBAIPath(instance.botId), manifest); const packageTeams = urlJoin(`work`, GBUtil.getGBAIPath(instance.botId), manifest);
if (!await GBUtil.exists(packageTeams)) { if (!(await GBUtil.exists(packageTeams))) {
GBLogEx.info(min, 'Generating MS Teams manifest....');
const data = await this.deployer.getBotManifest(instance); const data = await this.deployer.getBotManifest(instance);
fs.writeFile(packageTeams, data); await fs.writeFile(packageTeams, data);
} }
// Serves individual URL for each bot user interface. // Serves individual URL for each bot user interface.
@ -1098,7 +1117,7 @@ export class GBMinService {
const folder = `work/${path}/cache`; const folder = `work/${path}/cache`;
const filename = `${GBAdminService.generateUuid()}.png`; const filename = `${GBAdminService.generateUuid()}.png`;
fs.writeFile(urlJoin(folder, filename), data); await fs.writeFile(urlJoin(folder, filename), data);
step.context.activity.text = urlJoin( step.context.activity.text = urlJoin(
GBServer.globals.publicAddress, GBServer.globals.publicAddress,
`${min.instance.botId}`, `${min.instance.botId}`,
@ -1286,7 +1305,7 @@ export class GBMinService {
buffer = arrayBufferToBuffer(await res.arrayBuffer()); buffer = arrayBufferToBuffer(await res.arrayBuffer());
} }
fs.writeFile(localFileName, buffer); await fs.writeFile(localFileName, buffer);
return { return {
fileName: attachment.name, fileName: attachment.name,
@ -1723,4 +1742,65 @@ export class GBMinService {
createRpcServer(proxies, GBServer.globals.server.apiServer, opts); createRpcServer(proxies, GBServer.globals.server.apiServer, opts);
} }
// Map to track recent changes with timestamps
private recentChanges: Map<string, number> = new Map();
private async watchPackages(min: GBMinInstance, packageType) {
if (!GBConfigService.get('STORAGE_NAME')) {
const packagePath = GBUtil.getGBAIPath(min.botId, packageType);
const dirPath = path.join(GBConfigService.get('STORAGE_LIBRARY'), packagePath);
const watcher = chokidar.watch(dirPath, {
persistent: true,
ignoreInitial: true, // Ignore initial add events
depth: 99, // Watch subdirectories
awaitWriteFinish: {
stabilityThreshold: 500, // Wait for 500ms to ensure file is written completely
}
});
const WRITE_INTERVAL = 5000; // 5 seconds interval
// Function to handle file changes and prevent multiple calls within the 5-second window
const handleFileChange = async (filePath) => {
const now = Date.now();
// Add or update the file in the recent changes list
this.recentChanges.set(filePath, now);
// Clean up entries older than 5 seconds
for (const [key, timestamp] of this.recentChanges.entries()) {
if (now - timestamp > WRITE_INTERVAL) {
this.recentChanges.delete(key);
}
}
// If we have recent changes, deploy the package only once
if (this.recentChanges.size > 0) {
try {
await this.deployer.deployPackage2(min, null, dirPath);
} catch (error) {
GBLogEx.error(min, `Error deploying package: ${GBUtil.toYAML(error)}`);
}
// Delay further processing to prevent multiple deployments within 5 seconds
await new Promise(resolve => setTimeout(resolve, WRITE_INTERVAL));
// After the delay, clear only entries that were processed
this.recentChanges.clear();
}
};
// Watch for file changes
watcher.on('change', (filePath) => {
handleFileChange(filePath).catch(error => console.error('Error processing file change:', error));
});
}
}
} }

View file

@ -110,7 +110,7 @@ export class GBSSR {
const file = await fs.readFile(preferences, 'utf8'); const file = await fs.readFile(preferences, 'utf8');
const data = JSON.parse(file); const data = JSON.parse(file);
data['profile']['exit_type'] = 'none'; data['profile']['exit_type'] = 'none';
fs.writeFile(preferences, JSON.stringify(data)); await fs.writeFile(preferences, JSON.stringify(data));
} }
} }

View file

@ -441,9 +441,15 @@ export class KBService implements IGBKBService {
min: GBMinInstance, min: GBMinInstance,
packageId: number packageId: number
): Promise<GuaribasQuestion[]> { ): Promise<GuaribasQuestion[]> {
GBLogEx.info(min, `Now reading file ${filePath}...`); GBLogEx.info(min, `Now reading file ${path.basename(filePath)}...`);
const workbook = new Excel.Workbook(); const workbook = new Excel.Workbook();
const data = await workbook.xlsx.readFile(filePath);
let data;
if (filePath.endsWith('.xlsx')) {
data = await workbook.xlsx.readFile(filePath);
} else if (filePath.endsWith('.csv')) {
data = await workbook.csv.readFile(filePath);
}
let lastQuestionId: number; let lastQuestionId: number;
let lastAnswer: GuaribasAnswer; let lastAnswer: GuaribasAnswer;
@ -451,13 +457,15 @@ export class KBService implements IGBKBService {
// Finds a valid worksheet because Excel returns empty slots // Finds a valid worksheet because Excel returns empty slots
// when loading worksheets collection. // when loading worksheets collection.
let worksheet: any; let worksheet = data;
if (!worksheet) {
for (let t = 0; t < data.worksheets.length; t++) { for (let t = 0; t < data.worksheets.length; t++) {
worksheet = data.worksheets[t]; worksheet = data.worksheets[t];
if (worksheet) { if (worksheet) {
break; break;
} }
} }
}
const rows = worksheet._rows; const rows = worksheet._rows;
const answers = []; const answers = [];
@ -778,7 +786,7 @@ export class KBService implements IGBKBService {
path.basename(localName) path.basename(localName)
); );
const buffer = await image.read(); const buffer = await image.read();
fs.writeFile(localName, buffer, { encoding: null }); await fs.writeFile(localName, buffer, { encoding: null });
return { src: url }; return { src: url };
} }
}; };
@ -1098,15 +1106,12 @@ export class KBService implements IGBKBService {
} catch (error) { } catch (error) {
GBLogEx.info(min, `Ignore processing of ${file}. ${GBUtil.toYAML(error)}`); GBLogEx.info(min, `Ignore processing of ${file}. ${GBUtil.toYAML(error)}`);
} }
}); });
} }
files = await walkPromise(urlJoin(localPath, 'docs')); files = await walkPromise(urlJoin(localPath, 'docs'));
if (!files[0]) { if (files[0]) {
GBLogEx.info(min, `[GBDeployer] docs folder not created yet in .gbkb neither a website in .gbot.`);
} else {
await CollectionUtil.asyncForEach(files, async file => { await CollectionUtil.asyncForEach(files, async file => {
let content = null; let content = null;
let filePath = path.join(file.root, file.name); let filePath = path.join(file.root, file.name);
@ -1181,7 +1186,7 @@ export class KBService implements IGBKBService {
const files = await walkPromise(localPath); const files = await walkPromise(localPath);
await CollectionUtil.asyncForEach(files, async file => { await CollectionUtil.asyncForEach(files, async file => {
if (file !== null && file.name.endsWith('.xlsx')) { if (file !== null && (file.name.endsWith('.xlsx') || file.name.endsWith('.csv'))) {
return await this.importKbTabularFile(urlJoin(file.root, file.name), min, packageId); return await this.importKbTabularFile(urlJoin(file.root, file.name), min, packageId);
} }
}); });
@ -1340,7 +1345,7 @@ export class KBService implements IGBKBService {
public async deployKb(core: IGBCoreService, deployer: GBDeployer, localPath: string, min: GBMinInstance) { public async deployKb(core: IGBCoreService, deployer: GBDeployer, localPath: string, min: GBMinInstance) {
const packageName = path.basename(localPath); const packageName = path.basename(localPath);
const instance = await core.loadInstanceByBotId(min.botId); const instance = await core.loadInstanceByBotId(min.botId);
GBLogEx.info(min, `[GBDeployer] Importing: ${localPath}`); GBLogEx.info(min, `Publishing: ${path.basename(localPath)}`);
const p = await deployer.deployPackageToStorage(instance.instanceId, packageName); const p = await deployer.deployPackageToStorage(instance.instanceId, packageName);
await this.importKbPackage(min, localPath, p, instance); await this.importKbPackage(min, localPath, p, instance);
@ -1354,14 +1359,18 @@ export class KBService implements IGBKBService {
min['groupCache'] = await KBService.getGroupReplies(instance.instanceId); min['groupCache'] = await KBService.getGroupReplies(instance.instanceId);
await KBService.RefreshNER(min); await KBService.RefreshNER(min);
GBLogEx.info(min, `[GBDeployer] Start Bot Server Side Rendering... ${localPath}`); const ssr = min.core.getParam<boolean>(min.instance, 'SSR', false);
if (ssr) {
GBLogEx.info(min, `Start Bot Server Side Rendering... ${localPath}`);
const html = await GBSSR.getHTML(min); const html = await GBSSR.getHTML(min);
let packagePath = GBUtil.getGBAIPath(min.botId, `gbui`); let packagePath = GBUtil.getGBAIPath(min.botId, `gbui`);
packagePath = path.join(process.env.PWD, 'work', packagePath, 'index.html'); packagePath = path.join(process.env.PWD, 'work', packagePath, 'index.html');
GBLogEx.info(min, `[GBDeployer] Saving SSR HTML in ${packagePath}.`); GBLogEx.info(min, `Saving SSR HTML in ${packagePath}.`);
fs.writeFile(packagePath, html, 'utf8'); await fs.writeFile(packagePath, html, 'utf8');
}
GBLogEx.info(min, `[GBDeployer] Finished import of ${localPath}`); GBLogEx.info(min, `Done publishing of: ${localPath}.`);
} }
private async playAudio( private async playAudio(
@ -1434,7 +1443,6 @@ export class KBService implements IGBKBService {
directoryPath: string directoryPath: string
): Promise<string | null> { ): Promise<string | null> {
try { try {
// Check if the directory exists, create it if not. // Check if the directory exists, create it if not.
const directoryExists = await GBUtil.exists(directoryPath); const directoryExists = await GBUtil.exists(directoryPath);

View file

@ -188,7 +188,7 @@ export class ChatServices {
const gbaiName = GBUtil.getGBAIPath(min.botId, null); const gbaiName = GBUtil.getGBAIPath(min.botId, null);
const localName = path.join('work', gbaiName, 'cache', `img${GBAdminService.getRndReadableIdentifier()}.png`); const localName = path.join('work', gbaiName, 'cache', `img${GBAdminService.getRndReadableIdentifier()}.png`);
const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', path.basename(localName)); const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', path.basename(localName));
fs.writeFile(localName, buffer, { encoding: null }); await fs.writeFile(localName, buffer, { encoding: null });
return { localName: localName, url: url, data: buffer }; return { localName: localName, url: url, data: buffer };
} }
} }

View file

@ -73,7 +73,7 @@ export class ImageServices {
const url = response.data[0].url; const url = response.data[0].url;
const res = await fetch(url); const res = await fetch(url);
let buf: any = Buffer.from(await res.arrayBuffer()); let buf: any = Buffer.from(await res.arrayBuffer());
fs.writeFile(localName, buf, { encoding: null }); await fs.writeFile(localName, buf, { encoding: null });
GBLogEx.info(min, `DALL-E image generated at ${url}.`); GBLogEx.info(min, `DALL-E image generated at ${url}.`);

View file

@ -188,7 +188,7 @@ export class WhatsappDirectLine extends GBService {
'cache', 'cache',
`qr${GBAdminService.getRndReadableIdentifier()}.png` `qr${GBAdminService.getRndReadableIdentifier()}.png`
); );
fs.writeFile(localName, qrBuf.data); await fs.writeFile(localName, qrBuf.data);
const url = urlJoin(GBServer.globals.publicAddress, this.min.botId, 'cache', path.basename(localName)); const url = urlJoin(GBServer.globals.publicAddress, this.min.botId, 'cache', path.basename(localName));
if (adminNumber) { if (adminNumber) {
@ -331,7 +331,7 @@ export class WhatsappDirectLine extends GBService {
'cache', 'cache',
`tmp${GBAdminService.getRndReadableIdentifier()}.docx` `tmp${GBAdminService.getRndReadableIdentifier()}.docx`
); );
fs.writeFile(localName, buf, { encoding: null }); await fs.writeFile(localName, buf, { encoding: null });
const url = urlJoin(GBServer.globals.publicAddress, this.min.botId, 'cache', path.basename(localName)); const url = urlJoin(GBServer.globals.publicAddress, this.min.botId, 'cache', path.basename(localName));
attachments = []; attachments = [];

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -39,6 +39,7 @@ NLP Score,
Notes, Notes,
Open AI Key, Open AI Key,
Search Score, Search Score,
SSR,
Start Dialog, Start Dialog,
Store Answer Score, Store Answer Score,
Synchronize Database, Synchronize Database,
1 name value
39 Notes
40 Open AI Key
41 Search Score
42 SSR
43 Start Dialog
44 Store Answer Score
45 Synchronize Database

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View file

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB