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
JavaScript
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()} v.${app.version} (${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> | <a href="#tasks">Tasks</a> | <a href="#settings">Settings</a> | <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();