UNPKG

dynamicsmobile

Version:

Allows development of off-line mobile and web business apps over the Dynamics Mobile platform. More info on https://www.dynamicsmobile.com

515 lines (429 loc) 17.7 kB
const fs = require('fs'); const path = require('path'); const os = require('os'); var outputHtmlFileName = `dms-bo.html`; function generateMap() { let enumIndex = []; const rootDir = path.resolve("./");//SLA const boDir = path.resolve(path.join(rootDir, "./src/Business Objects")); const extDir = path.resolve(path.join(rootDir, "./ext/Business Objects")); var boFiles = fs.readdirSync(boDir).filter(f => f.indexOf('.bo.json') > 0);; //fetch all extenion business objects var _extBoFiles = []; if (fs.existsSync(extDir)) _extBoFiles = fs.readdirSync(extDir).filter(f => f.indexOf('.bo.json') > 0);; //check if new extended bo exists _extBoFiles.forEach( f => { if (boFiles.indexOf(f) < 0) { boFiles.push(f); } } ) var outputHtml = ` <br/> <h2 id="bo">Business Objects (${boFiles.length})</h2> `; outputHtml += '<ul class="content-index">'; boFiles.forEach(function (boFile) { const name = path.basename(boFile).split('.')[0]; outputHtml += `<li><a href="#${name}"/>${name}</a></li>` }); outputHtml += '</ul><hr/>' const enums = []; boFiles.sort(); boFiles.forEach(function (boFile) { var tokens = boFile.split('.'); if (tokens.length == 3 && tokens[2] == 'json') { var boName = tokens[0]; var actualBoDir; if (fs.existsSync(path.join(extDir, boFile))) actualBoDir = extDir; else actualBoDir = boDir; var bo; try { bo = JSON.parse(fs.readFileSync(path.join(actualBoDir, boFile), 'utf8')); } catch (err) { console.log('ERROR: Can not load business object file: ' + boFile); console.log(); process.exit(1); return; } bo = bo[boName]; for (var key in bo.properties) { var prop = bo.properties[key]; //let fieldTypeInfo = prop.type; if (prop.List) { enumIndex.push(`<li><a href="#${bo.name}${key}enum">${bo.name}_${key}_enum</a></li>`) } } boOutput = `<a href="#bosection">back to top</a>`; boOutput += `<h2 class="bo-title" id="${bo.name}">${bo.syncTag ? bo.syncTag : boName}</h2>`; boOutput += `<table> <tr><td><strong>Sync Name</strong></td><td>${bo.syncTag ? bo.syncTag : boName}</td></tr> <tr><td><strong>Name</strong></td><td>${boName}</td></tr> <tr><td><strong>Direction</strong></td><td>${bo.syncDirection ? bo.syncDirection : 'both'}</td></tr> <tr><td><strong>Help</strong></td><td>${bo.help ? bo.help : ''}</td></tr> </table>` boOutput += '<table class="bo-table">'; boOutput += '<thead><tr class="bo-header-row">'; boOutput += '<td class="bo-header-cell">Sync Name</td>'; boOutput += '<td class="bo-header-cell">Name</td>'; boOutput += '<td class="bo-header-cell">Type</td>'; boOutput += '<td class="bo-header-cell">Required</td>'; boOutput += '<td class="bo-header-cell">Size</td>'; boOutput += '<td class="bo-header-cell">Label</td>'; boOutput += '<td class="bo-header-cell">Help</td>'; boOutput += '</tr><thead>'; boOutput += '<tbody>'; for (var key in bo.properties) { var prop = bo.properties[key]; let fieldTypeInfo = prop.type; if (prop.List) { fieldTypeInfo = `<a href="#${bo.name}${key}enum">${bo.name}_${key}_enum</a> (${prop.type})` enums.push(`<a href="#bosection">back to top</a>`); enums.push(`<h3 id="${bo.name}${key}enum">${bo.name}_${key}_enum</h3>`); enums.push(`<strong>Business Object:</strong> <a href="#${bo.name}">${bo.name}</a><br/>`); enums.push(`<strong>Property:</strong> ${key}<br/>`); enums.push('<table class="bo-table"><thead>'); enums.push(`<tr><td class="bo-header-cell">Label</td><td class="bo-header-cell">Value</td></tr>`); enums.push('</thead><tbody>'); prop.List.forEach(element => { enums.push(`<tr><td class="bo-prop-cell">${element.Label}</td><td class="bo-prop-cell">${element.Code}</td></tr>`); }); enums.push('</tbody></table>'); enums.push(`<br/>`) } boOutput += `<tr><td class="bo-prop-cell">${prop.syncTag ? prop.syncTag : key}</td><td class="bo-prop-cell">${key}</td><td class="bo-prop-cell">${fieldTypeInfo}</td><td class="bo-prop-cell">${prop.required ? 'Yes' : ''}</td><td class="bo-prop-cell">${prop.length ? prop.length : ''}</td><td class="bo-prop-cell">${prop.label}</td><td class="bo-prop-cell">${prop.help ? prop.help : ''}</td></tr>` }; boOutput += '<tbody>'; boOutput += '</table><br/>'; outputHtml += boOutput; } }) outputHtml += "<hr/>"; outputHtml += "<h2>Enums</h2>"; outputHtml += "<hr/>"; outputHtml += `<ul class="content-index">${enumIndex.join('')}</ul>`; outputHtml += "<hr/>"; outputHtml += "The following section contains list of enum types. The enum types are generated from business object properties, which have a List attribute."; outputHtml += "<br/><br/>" + enums.join(''); outputHtml += '</body></html>'; return outputHtml }; function generateTasks() { const rootDir = path.resolve("./");//SLA const taskDir = path.resolve(path.join(rootDir, "./src/Tasks")); const extDir = path.resolve(path.join(rootDir, "./ext/Tasks")); var app = fs.readFileSync('./package.json', 'utf8'); app = JSON.parse(app); var taskFiles = fs.readdirSync(taskDir).filter(f => f.indexOf('.task.json') > 0); //fetch all extenion tasks var _extTaskFiles = []; if (fs.existsSync(extDir)) _extTaskFiles = fs.readdirSync(extDir).filter(f => f.indexOf('.task.json') > 0); //check if new extended task exists _extTaskFiles.forEach( f => { if (taskFiles.indexOf(f) < 0) { taskFiles.push(f); } } ) taskFiles.sort(); var outputHtml = ` <br/> <h2 id="tasks">Tasks (${taskFiles.length})</h2> `; outputHtml += '<ul class="content-index">'; taskFiles.forEach(function (boFile) { const name = path.basename(boFile).split('.')[0]; outputHtml += `<li><a href="#${name}"/>${name}</a></li>` }); outputHtml += '</ul><hr/>' taskFiles.forEach(function (taskFile) { var tokens = taskFile.split('.'); if (tokens.length == 3 && tokens[2] == 'json') { var taskName = tokens[0]; if (taskName != 'Home' && taskName != 'Index') { var actualTaskDir; if (fs.existsSync(path.join(extDir, taskFile))) actualTaskDir = extDir; else actualTaskDir = taskDir; var task; try { task = JSON.parse(fs.readFileSync(path.join(actualTaskDir, taskFile), 'utf8')); } catch (err) { console.log('Can not load task file: ' + path.join(actualTaskDir, taskFile)); process.exit(1); return; } taskOutput = `<a href="#tasksection">back to top</a>`; taskOutput += `<h2 style="margin-bottom:1px" class="bo-title" id="${task.id}">Task: ${task.name ? task.name : task.id}</h2>`; taskOutput += `<div style="margin-top:1px">${task.help ? task.help : ''}</div>`; taskOutput += `<div style="margin-top:1px">Steps: ${task.steps.length}</div>`; // taskOutput += '<table class="bo-table">'; // taskOutput += '<thead><tr class="bo-header-row">'; // taskOutput += '<td class="bo-header-cell">Step Name</td>'; // taskOutput += '<td class="bo-header-cell">Step Routes</td>'; // taskOutput += '</tr><thead>'; // taskOutput += '<tbody>'; // task.steps.forEach(step => { // taskOutput += `<tr><td class="bo-prop-cell">${step.id}</td><td class="bo-prop-cell">${step.routes.length}</td></tr>` // }) // taskOutput += '</tbody>'; // taskOutput += '</table>'; taskOutput += `<br/><div style="margin-bottom:2px;font-size:0.8em">Task deny rule</div> <pre class="code" style="margin-top:2px;"> { "resource": "app://${app.name.toUpperCase()}/${task.id}/*", "operation": "*", "action": "deny" }</pre> ` taskOutput += `<div style="margin-bottom:2px;font-size:0.8em">Task allow rule</div> <pre class="code" style="margin-top:2px;"> { "resource": "app://${app.name.toUpperCase()}/${task.id}/*", "operation": "*", "action": "allow" } </pre> ` outputHtml += taskOutput; } } }) return outputHtml; }; function generateLanguages() { const rootDir = path.resolve("./");//SLA const translDir = path.resolve(path.join(rootDir, "./src/Translations")); const extDir = path.resolve(path.join(rootDir, "./ext/Translations")); const unniqueLanguages = []; langFiles = []; if (fs.existsSync(translDir)) langFiles = fs.readdirSync(translDir).filter(f => f.indexOf('.lang.json') > 0); extLangFiles = []; if (fs.existsSync(extDir)) extLangFiles = fs.readdirSync(extDir).filter(f => f.indexOf('.lang.json') > 0); langFiles.forEach(f=>{ unniqueLanguages.push(f); }) extLangFiles.forEach(f1=>{ const f = unniqueLanguages.find(f2=>f1==f2) if(!f) unniqueLanguages.push(f1); }) var langsContent = '<ul>'; unniqueLanguages.forEach(l=>{ langsContent+=`<li>${l.split('.')[0]}</li>` }); langsContent+='</ul>' return `<br/> <h2 id="languages">Languages (${unniqueLanguages.length})</h2> ${langsContent}<br/> <a href="#bosection">back to top</a> ` } function generateSettingsJson() { const rootDir = path.resolve("./");//SLA const settingsDir = path.resolve(path.join(rootDir, "./src/settings.json")); const extDir = path.resolve(path.join(rootDir, "./ext/settings.json")); var settings, exSettings; if (fs.existsSync(settingsDir)) settings = JSON.parse(fs.readFileSync(settingsDir)); else settings = {}; if (fs.existsSync(exSettings)) exSettings = JSON.parse(fs.readFileSync(extDir)); else exSettings = {}; //combine settings and extSettings const keys = Object.getOwnPropertyNames(settings); keys.forEach(key => { if (settings[key] && !exSettings[key]) exSettings[key] = settings[key] }) return exSettings; } function generateSettingsHtml() { // const rootDir = path.resolve("./");//SLA // const settingsDir = path.resolve(path.join(rootDir, "./src/settings.json")); // const extDir = path.resolve(path.join(rootDir, "./ext/settings.json")); // var settings, exSettings; // if (fs.existsSync(settingsDir)) // settings = JSON.parse(fs.readFileSync(settingsDir)); // else // settings = {}; // if (fs.existsSync(exSettings)) // exSettings = JSON.parse(fs.readFileSync(extDir)); // else // exSettings = {}; // //combine settings and extSettings // const keys = Object.getOwnPropertyNames(settings); // keys.forEach(key => { // if (settings[key] && !exSettings[key]) // exSettings[key] = settings[key] // }) const exSettings = generateSettingsJson(); const allKeys = Object.getOwnPropertyNames(exSettings); allKeys.sort(); var outputHtml = ` <br/> <h2 id="settings">Settings (${allKeys.length})</h2> `; outputHtml += '<ul class="content-index">'; allKeys.forEach(function (key) { outputHtml += `<li><a href="#${key}"/>${key}</a></li>` }); outputHtml += '</ul><hr/>' outputHtml += '<table class="bo-table">'; outputHtml += '<thead><tr class="bo-header-row">'; outputHtml += '<td class="bo-header-cell">Setting</td>'; outputHtml += '<td class="bo-header-cell">Values</td>'; outputHtml += '<td class="bo-header-cell">Default Value</td>'; outputHtml += '<td class="bo-header-cell">Required</td>'; outputHtml += '<td class="bo-header-cell">Help</td>'; outputHtml += '</tr><thead>'; outputHtml += '<tbody>'; allKeys.forEach(function (key) { var setting = exSettings[key]; outputHtml += `<tr><td id="${key}" class="bo-prop-cell">${key}</td><td class="bo-prop-cell">${setting.values ? setting.values : ''}</td><td class="bo-prop-cell">${setting.defaultValue ? setting.defaultValue : ''}</td><td class="bo-prop-cell">${setting.required ? 'Yes' : 'No'}</td><td class="bo-prop-cell">${setting.help}</td></tr>` }) outputHtml += '</tbody>'; outputHtml += '</table>'; outputHtml += `You can add the settings after you login to <a href="https://portal.dynamicsmobile.com" target="_blank">https://portal.dynamicsmobile.com</a> and navgate to Administration/Roles. Open the role where the users are operating and add the settings in the settings section of the role. Checkout the following documentation for more info: <ul> <li><a href="https://docs.dynamicsmobile.com/dynamics-mobile-portal/user-guide/roles" target="_blank">Role</a></li> <li><a href="https://docs.dynamicsmobile.com/dynamics-mobile-portal/user-guide/roles/role-settings" target="_blank">Role Settings</a></li> </ul><br/> <a href="#bosection">back to top</a> `; return outputHtml; }; function generateAll() { var app = fs.readFileSync('./package.json', 'utf8'); app = JSON.parse(app); let dontOpenMap = false; let commandLineArgs = process.argv; if (commandLineArgs && commandLineArgs.length > 0 && commandLineArgs.indexOf("--silent") > 0) { dontOpenMap = true; } const map = generateMap(); const tasks = generateTasks(); const settings = generateSettingsHtml(); const settingsJson = generateSettingsJson(); const languages = generateLanguages(); const homedir = os.homedir(); const profileFolder = path.join(homedir, '.dms'); var profileObj = null; var profilePath = path.join(profileFolder, 'profile.cfg'); if (fs.existsSync(profilePath)) { const profile = fs.readFileSync(profilePath, 'utf8'); try { profileObj = JSON.parse(profile); } catch (err) { } } var outputHtmlFileName = `dms-bo.html`; var outputHtml = `<html><head><title>Dynamics Mobile Application Spec</title> <style> body { padding: 1em; } .content-index { list-style-type: none; margin: 0; padding: 0; overflow: hidden; } .content-index li { float: left; margin-right:10px; } .bo-title { color: gray; } .bo-table { border: 1px Solid Black; border-collapse: collapse; } .bo-header-row { border: 1px Solid Black; border-collapse: collapse; } .bo-header-cell { min-width: 100px; padding: 4px; border: 1px Solid Black; border-collapse: collapse; background-color: #214860; color: white; text-align: center; } .bo-prop-cell { padding: 4px; border: 1px Solid Black; border-collapse: collapse; } .code { background-color: #EDF2F5; font-size: 0.8em; padding:10px; } </style> <script> function toggleSeciton(id, title){ console.log(document.getElementById('bosectionbtn')) const s = document.getElementById(id).style.display; if(s=='none'){ document.getElementById(id).style.display = 'block'; document.getElementById(id+'btn').innerHTML = '- Hide '+title; } else { document.getElementById(id).style.display = 'none'; document.getElementById(id+'btn').innerHTML = '+ Show '+title; } } </script> </head><body> <br/> <h1>Dynamics Mobile Application Spec</h1> <h3>${app.name.toUpperCase()}&nbsp;v.${app.version}&nbsp;(${app.dms.appType == 'm' ? 'mobile app' : 'backend app'})</h3> <p>The document contains application technical documentation to be used by business integrators.<br/> The document is generated with <a href="https://www.npmjs.com/package/dynamicsmobile">Dynamics Mobile SDK</a></p> <p> App Area: ${profileObj ? profileObj.appArea : ''}<br/>SDK: v.${app.dependencies.dynamicsmobile}<br/>Generated on ${(new Date().toUTCString())}</p> <hr/> <span id="top"></span> Section index: <a href="#bo">Business Objects</a>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;<a href="#tasks">Tasks</a>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;<a href="#settings">Settings</a>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;<a href="#languages">Languages</a> <hr/> <button id="bosectionbtn" onclick="javascirpt: toggleSeciton('bosection','Business Objects')"> - Hide Business Objects</button><br/> <div id="bosection">${map}</div><br/> <button id="tasksectionbtn" onclick="javascirpt: toggleSeciton('tasksection', 'Tasks')"> - Hide Tasks</button><br/> <div id="tasksection">${tasks}</div><br/> <button id="settingssectionbtn" onclick="javascirpt: toggleSeciton('settingssection', 'Settings')"> - Hide Settings</button><br/> <div id="settingssection">${settings}</div><br/> <button id="languagesectionbtn" onclick="javascirpt: toggleSeciton('languagesection', 'Languages')"> - Hide Languages</button><br/> <div id="languagesection">${languages}</div> </body> </html> `; const actualOutputDir = path.resolve("./.bin"); if (!fs.existsSync(actualOutputDir)) { fs.mkdirSync(actualOutputDir) } fs.writeFileSync(path.join(actualOutputDir, outputHtmlFileName), outputHtml, 'utf8'); fs.writeFileSync(path.join(actualOutputDir, 'settings.json'), JSON.stringify(settingsJson), 'utf8'); if (!dontOpenMap) require("openurl").open("file://" + path.resolve(path.join(actualOutputDir, outputHtmlFileName))); }; generateAll();