dynamicsmobile
Version:
Allows development of off-line mobile and web business apps over the Dynamics Mobile platform. More info on https://www.dynamicsmobile.com
957 lines (831 loc) • 37.7 kB
JavaScript
/**
* Dynamics Mobile
* www.dynamicsmobile.com
*
* webpack plugin used to package mobile appsfor the dynamics mobile platform
*/
const path = require('path');
const fs = require("fs");
const cpx = require('cpx2');
const outputPath = ".bin/user/apparea/SANDBOX/APP/en";
const chalk = require('chalk');
const os = require('os');
const crypto = require('crypto');
const { lang, locale } = require('moment');
let languageCode = "en";
var languages = { 'en': {} };
console.log('');
console.log(chalk.white.bgGreen(' DMS '), 'Dynamics Mobile Application Code Generator is working...');
var isMobileApp = false;
class DmsAppWebPackPlugin {
static parseComponentPathAndClass(taskId, stepId, componentHtml) {
//determine component path - e.g. path to the component implementation
var pathAttr = componentHtml.match(/path=\"([^"]*)\"/i);
if (!pathAttr || pathAttr.length == 0)
throw `DMS COMPONENT ERROR 102: Component ${taskId}/${stepId} does not have path attribute!`
pathAttr = pathAttr[1]
//determine component class name
var componentClassName = pathAttr;
if (componentClassName.indexOf('/') > 0) {
componentClassName = componentClassName.split('/');
componentClassName = componentClassName[componentClassName.length - 1];
}
return { componentPath: pathAttr, componentClassName: componentClassName };
}
static parseComponentId(componentHtml, componentClassName) {
//determine component dom id attribute
var componentIdAttr = componentHtml.match(/id=\"([^"]*)\"/i);
if (!componentIdAttr || componentIdAttr.length == 0) {
componentIdAttr = componentClassName
}
else {
componentIdAttr = componentIdAttr[0].split('=');
componentIdAttr = componentIdAttr[1].replace(/"/g, '');
}
return componentIdAttr;
}
static parseCustomArgs(taskId, stepId, stepHtml, componentHtml) {
var allCustomArguments = {};
var regex = new RegExp(/\[([^"]*)\]=\"([^"]*)\"/gmi);
let m;
while ((m = regex.exec(componentHtml)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
var customArgumentName = m[1];
var customArgumentValue = m[2];
allCustomArguments[customArgumentName] = customArgumentValue;
}
return allCustomArguments;
}
static processOneComponent(taskId, stepId, stepHtml, componentHtml, componentsCode, componentImportTs) {
var { componentPath, componentClassName } = DmsAppWebPackPlugin.parseComponentPathAndClass(taskId, stepId, componentHtml)
var componentId = DmsAppWebPackPlugin.parseComponentId(componentHtml, componentClassName);
var customArguments = DmsAppWebPackPlugin.parseCustomArgs(taskId, stepId, stepHtml, componentHtml);
var htmlComponentFilePath = path.resolve(`./ext/${componentPath}.html`);
if (!fs.existsSync(htmlComponentFilePath)) {
htmlComponentFilePath = path.resolve(`./src/${componentPath}.html`);
}
var isExt = true;
var componentFilePathTs = path.resolve(`./ext/${componentPath}.ts`);
if (!fs.existsSync(componentFilePathTs)) {
isExt = false;
componentFilePathTs = path.resolve(`./src/${componentPath}.html`);
}
if (!fs.existsSync(htmlComponentFilePath))
throw `DMS COMPONENT ERROR: Component has invaid path attribute: ${taskId}/${stepId}> ${htmlComponentFilePath}!`
var componentRawHtml = fs.readFileSync(htmlComponentFilePath, 'utf8');
componentsCode.push(`{
componentPath:'${componentPath}',
componentClassName:'${componentClassName}',
componentClass:${componentClassName},
domId:'${componentId}',
customArguments:${JSON.stringify(customArguments)}
}`);
var importStatement = `import {${componentClassName}} from '../../../../../../${isExt ? 'ext' : 'src'}/${componentPath}'; `
if (componentImportTs.indexOf(importStatement) < 0) {
componentImportTs.push(importStatement);
}
var componentModifiedHtml = componentRawHtml.replace(/\[\[stepId\]\]/g, stepId).replace(/\[\[taskId\]\]/g, taskId);
var keys = Object.getOwnPropertyNames(customArguments);
keys.forEach(function (key) {
var regEx = new RegExp(`{{${key}}}`, 'gi');
//componentModifiedHtml = componentModifiedHtml.replace(/\{\{$\{key\}\}\}/gi, customArguments[key])
componentModifiedHtml = componentModifiedHtml.replace(regEx, customArguments[key])
});
var __stepHtml;
if (componentModifiedHtml.indexOf('<!--dms-renderin:app-->') >= 0) {
componentModifiedHtml = componentModifiedHtml.replace('<!--dms-renderin:app-->', '');
__stepHtml = stepHtml.replace(componentHtml, '');
return { modifiedStepHtml: __stepHtml, taskLevelHtml: `<!-- ko using: ${componentId} --> ${componentModifiedHtml} <!-- /ko -->` };
}
else {
__stepHtml = stepHtml.replace(componentHtml, `<!-- ko using: ${componentId} --> ${componentModifiedHtml} <!-- /ko -->`);
return { modifiedStepHtml: __stepHtml, taskLevelHtml: '' }
}
}
static processComponents(taskId, stepId, stepHtml, componentImportTs) {
var modifiedStepHtml = stepHtml;
var componentsCode = [];
var componentImportTs = [];
var componentRegEx = new RegExp('<\s*dms-component[^>]*>(.*?)<\s*/\s*dms-component>', 'gim');
var taskLevelControls = '';
let m;
while ((m = componentRegEx.exec(stepHtml)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === componentRegEx.lastIndex) {
componentRegEx.lastIndex++;
}
var { modifiedStepHtml, taskLevelHtml } = DmsAppWebPackPlugin.processOneComponent(taskId, stepId, modifiedStepHtml, m[0], componentsCode, componentImportTs);
taskLevelControls += taskLevelHtml;
}
return { viewHtml: modifiedStepHtml, components: componentsCode, componentImportTs: componentImportTs, taskLevelHtml: taskLevelControls };
}
generateReleaseNotes(app, outputPath) {
return
//************* release notes
let rn;
if (!fs.existsSync(path.resolve('./release-notes.json'))) {
const dt = new Date();
rn = [
{
version: "1.0.0",
description: "general description of the release goes here",
date: `${dt.getFullYear()}-${dt.getMonth() + 1}-${dt.getDate()}`,
features: ["feature 1", "feature 2"],
fixes: ["fix 1", "fix 2"],
internal: ["internal 1", "internal 2"],
instructions: "any additional deploy instructions or notes come here"
}
];
fs.writeFileSync(path.resolve('./release-notes.json'), JSON.stringify(rn));
} else {
rn = JSON.parse(fs.readFileSync(path.resolve('./release-notes.json')));
}
if (!Array.isArray(rn) || rn.length == 0) {
console.log(chalk.white.bgRed('DMS ERROR: '), ' File ./release-notes.json must contain a json array and must contain at least one element');
process.exit(1);
return;
}
//find current version in release notes
const entryForCurrentVersion = rn.find(v => v.version == app.version);
if (entryForCurrentVersion == null) {
console.log(chalk.white.bgRed('DMS ERROR: '), ' File ./release-notes.json does not have entry for current app version ' + app.version);
process.exit(1);
return;
}
//check entry fields
if (!entryForCurrentVersion.date) {
console.log(chalk.white.bgRed('DMS ERROR: '), ` File ./release-notes.json, version ${app.version} does not contain date field`);
process.exit(1);
return;
}
//check description field
if (entryForCurrentVersion.description == undefined) {
console.log(chalk.white.bgRed('DMS ERROR: '), ` File ./release-notes.json, version ${app.version} does not contain description field`);
process.exit(1);
return;
}
//check features field
if (!entryForCurrentVersion.features) {
console.log(chalk.white.bgRed('DMS ERROR: '), ` File ./release-notes.json, version ${app.version} does not contain features field`);
process.exit(1);
return;
}
//check fixes field
if (!entryForCurrentVersion.fixes) {
console.log(chalk.white.bgRed('DMS ERROR: '), ` File ./release-notes.json, version ${app.version} does not contain fixes field`);
process.exit(1);
return;
}
//check instructions field
if (entryForCurrentVersion.instructions == undefined) {
console.log(chalk.white.bgRed('DMS ERROR: '), ` File ./release-notes.json, version ${app.version} does not contain instructions field`);
process.exit(1);
return;
}
//check instructions field
if (!entryForCurrentVersion.internal) {
console.log(chalk.white.bgRed('DMS ERROR: '), ` File ./release-notes.json, version ${app.version} does not contain internal field`);
process.exit(1);
return;
}
fs.writeFileSync(path.join(outputPath, './release-notes.json'), JSON.stringify(rn));
let releaseNotesHtml = `
<div class="rn">
<div class="rn-header">
<div class="rn-header-title">
<h1 style="margin-bottom: 1px;">Dynamics Mobile Release Notes</h1>
<a href="https://www.dynamcsmobile.com", title="dynamcis mobile website">www.dynamicsmobile.com</a>
</div>
<div style="margin-top: 20px;">
<h3>Application: ${app.name.toUpperCase()}</h3>
</div>
</div>
<div class="rn-content" style="padding-left: 30px">`;
rn.sort((a, b) => { return b.version.localeCompare(a.version) }).forEach(v => {
releaseNotesHtml += `<br/><div>`
releaseNotesHtml += `<h2>${v.version}<span><small> (${v.date})</small></span></h2><hr/>`
if (v.description) {
releaseNotesHtml += `<h3>Description</h3>`;
releaseNotesHtml += `<p style="padding-left: 30px">${v.description}</p>`;
}
releaseNotesHtml += `<h3>Features</h3>`;
releaseNotesHtml += `<ul style="padding-left: 30px">`;
v.features.forEach(f => {
releaseNotesHtml += `<li>${f}</li>`;
});
releaseNotesHtml += `</ul>`;
releaseNotesHtml += `<h3>Fixes</h3>`;
releaseNotesHtml += `<ul style="padding-left: 30px">`;
v.fixes.forEach(f => {
releaseNotesHtml += `<li>${f}</li>`;
});
releaseNotesHtml += `</ul>`;
if (v.instructions) {
releaseNotesHtml += `<h3>Instructions</h3>`;
releaseNotesHtml += `<div style="padding-left: 30px"><p>${v.instructions}</p></div>`;
}
releaseNotesHtml += `</div>`
});
releaseNotesHtml += `
</div>
</div>`;
fs.writeFileSync(path.join(outputPath, './release-notes.html'), releaseNotesHtml);
}
entries(rootPath, compilationProfileName,taskBatch) {
if (!fs.existsSync(".bin")) {
fs.mkdirSync(".bin")
}
if (!fs.existsSync(".bin/user")) {
fs.mkdirSync(".bin/user")
}
if (!fs.existsSync(".bin/user/apparea")) {
fs.mkdirSync(".bin/user/apparea")
}
if (!fs.existsSync(".bin/user/apparea/SANDBOX")) {
fs.mkdirSync(".bin/user/apparea/SANDBOX")
}
if (!fs.existsSync(".bin/user/apparea/SANDBOX/APP")) {
fs.mkdirSync(".bin/user/apparea/SANDBOX/APP")
}
if (!fs.existsSync(".bin/user/apparea/SANDBOX/APP/en")) {
fs.mkdirSync(".bin/user/apparea/SANDBOX/APP/en")
}
if (!fs.existsSync(".bin/user/apparea/SANDBOX/APP/en/Tasks")) {
fs.mkdirSync(".bin/user/apparea/SANDBOX/APP/en/Tasks")
}
//copy login task and views into the app - used in simulator only
//this must be only on REFRESH..e.g. initiated by developer only
// cpx.copySync(`./node_modules/dynamicsmobile/templates/${isMobileApp ? 'mobile-app' : 'backend-app'}/dms/templates/logintask/*`, path.join(rootPath, 'Tasks'));
// cpx.copySync(`./node_modules/dynamicsmobile/templates/${isMobileApp ? 'mobile-app' : 'backend-app'}/dms/templates/loginview/*`, path.join(rootPath, 'Views/Index'));
// cpx.copySync(`./node_modules/dynamicsmobile/templates/${isMobileApp ? 'mobile-app' : 'backend-app'}/dms/templates/logintask/*`, path.join(rootPath, 'Tasks'));
// cpx.copySync(`./node_modules/dynamicsmobile/templates/${isMobileApp ? 'mobile-app' : 'backend-app'}/dms/templates/loginview/*`, path.join(rootPath, 'Views/Index'));
var homedir = os.homedir();
var folder = path.join(homedir, '.dms');
var profilePath = path.join(folder, 'profile.cfg');
if (!fs.existsSync(profilePath)) {
console.log('DMS ERROR: Dynamics Mobile profile does not exists. Use "dms login" command from command line, first');
process.exit(1);
return;
}
var profile = fs.readFileSync(profilePath, 'utf8');
try {
profile = JSON.parse(profile);
}
catch (err) {
console.log(chalk.red('DMS ERROR: Dynamics Mobile profile is invalid. Use "dms login" from the command line!'));
console.log();
process.exit(1);
return;
}
//override index.ts
// let cnt = fs.readFileSync('./src/Views/Index/Index.ts', 'utf8');
// if (profile.user)
// cnt = cnt.replace('{{decideidnetity2}', '\t//identity 1.0\n\t\treturn;');
// else
// cnt = cnt.replace('{{decideidnetity2}', '\t//identity 2.0\n');
//fs.writeFileSync('./src/Views/Index/Index.ts', cnt, 'utf8');
cpx.copySync('./src/Tasks/*.task.json', path.join(outputPath, 'Tasks'));
cpx.copySync('./ext/Tasks/*.task.json', path.join(outputPath, 'Tasks'));
cpx.copySync('./.tmp/Tasks/*.task.json', path.join(outputPath, 'Tasks'));
cpx.copySync('./src/Data/sync.json', outputPath);
cpx.copySync('./ext/Data/sync.json', outputPath);
cpx.copySync('./src/manifest.json', outputPath);
cpx.copySync('./ext/manifest.json', outputPath);
var inputEntries = {};
var actualRootDir = path.resolve(rootPath, './Tasks');
var actualExtRootDir = path.resolve(rootPath, '../ext/Tasks');
var actualTmpRootDir = path.resolve(rootPath, '../.tmp/Tasks');
var taskFiles = fs.readdirSync(actualRootDir);
var extTaskFiles = [];
var tmpTaskFiles = [];
if (fs.existsSync(actualExtRootDir))
extTaskFiles = fs.readdirSync(actualExtRootDir);
taskFiles.push(...extTaskFiles.filter(extTask => { return taskFiles.indexOf(extTask) < 0 }))
if (fs.existsSync(actualTmpRootDir))
tmpTaskFiles = fs.readdirSync(actualTmpRootDir);
taskFiles.push(...tmpTaskFiles.filter(tmpTask => { return taskFiles.indexOf(tmpTask) < 0 }))
var app;
try {
app = JSON.parse(fs.readFileSync('./package.json'));
}
catch (err) {
throw 'package.json file has invalid json format!';
}
if (!app.name) {
throw new Error('DMS App compile error 1001: File package.json must have attribute "name"\n');
}
if (!app.dms) {
app.dms = {};
}
if (!app.dms.locales) {
app.dms.locales = ['en'];
}
if (app.dms && app.dms.language) {
languageCode = app.dms.language
}
else {
languageCode = 'en';
console.log('Property language is missing in package.json/dms. Switching to language "en"');
}
isMobileApp = (app.dms && app.dms.appType == 'm') ? true : false;
for (const idx in app.dms.locales) {
const localeCode = app.dms.locales[idx];
const languageFile = fs.readFileSync(path.resolve('./src/locales', `${localeCode}.json`), 'utf8');
try {
const langContent = JSON.parse(languageFile);
languages[localeCode] = langContent;
if (fs.existsSync(path.resolve('./ext/locales', `${localeCode}.json`))) {
const extLanguageFile = fs.readFileSync(path.resolve('./ext/locales', `${localeCode}.json`), 'utf8');
if (extLanguageFile) {
try {
const extLangContent = JSON.parse(extLanguageFile);
for (const key in extLangContent) {
if (extLangContent[key]) {
languages[localeCode][key] = extLangContent[key];
}
}
}
catch (err) {
throw `Language file ${localeCode}.json has invalid JSON content>${err}`;
}
}
}
}
catch (err) {
throw `Language file ${localeFile} has invalid JSON content>${err}`;
}
}
if (!isMobileApp) {
//root menu prefixed tasks processing
let menuAsString;
if (fs.existsSync(path.resolve('./src', 'root-menu.json'))) {
menuAsString = fs.readFileSync(path.resolve('./src/root-menu.json'), 'utf8');
}
else
if (fs.existsSync(path.resolve('./ext', 'root-menu.json'))) {
menuAsString = fs.readFileSync(path.resolve('./ext', 'root-menu.json'), 'utf8');
}
if (!menuAsString) {
throw new Error('DMS App compile error 1002: File root-menu.json is missing in src or ext folder');
}
const menuContent = JSON.parse(menuAsString);
const processItem = (item) => {
if (item.items && item.items.length > 0) {
for (let i = 0; i < item.items.length; i++) {
processItem(item.items[i]);
}
}
else
if (item.taskPrefix && item.taskId) {
item.taskId = item.taskPrefix + '_' + item.taskId;
}
}
for (let i = 0; i < menuContent.items.length; i++) {
processItem(menuContent.items[i])
}
//root menu language processing
if (menuContent) {
for (const localeCode in languages) {
console.log('Processing root menu for locale:', localeCode);
let menu = JSON.stringify(menuContent);
for (const key in languages[localeCode]) {
var lngRegEx = new RegExp(`{#${key}#}`, 'gi');
if (languages[localeCode][key]) {
const safeKey = languages[localeCode][key].replace(/</g, '<').replace(/>/g, '>').replace(/\//g, '/').replace(/\n/g, ' ').replace(/\t/g, ' ').replace(/'/g, ''').replace(/"/g, '"');
menu = menu.toString().replace(lngRegEx, safeKey);
}
}
if (!fs.existsSync(path.join(outputPath.replace('/en', '/' + localeCode)))) {
fs.mkdirSync(path.join(outputPath.replace('/en', '/' + localeCode)));
}
fs.writeFileSync(path.join(outputPath.replace('/en', '/' + localeCode), 'root-menu.json'), menu);
}
}
}
cpx.copySync('./src/service-worker.js', outputPath);
cpx.copySync('./ext/service-worker.js', outputPath);
if (fs.existsSync(path.join(outputPath, 'service-worker.js'))) {
let syncWorkerContent = fs.readFileSync(path.join(outputPath, 'service-worker.js'), 'utf8')
syncWorkerContent = syncWorkerContent.replace('{{appVersion}}', app.version);
fs.writeFileSync(path.join(outputPath, 'service-worker.js'), syncWorkerContent, 'utf8');
}
if (app && app.dms && app.dms.import) {
console.log('');
console.log('Importing resources from package.json/dms/import:');
console.log('----------------------------');
app.dms.import.forEach(function (item) {
if (typeof (item) == 'string') {
const p = path.resolve(item);
console.log('** Importing ' + p, ' to ', outputPath);
cpx.copySync(item, outputPath);
}
else if (typeof (item) == 'object') {
if (!item.src) {
throw new Error('Importing item is missing src attribute');
}
if (!item.dest) {
throw new Error('Importing item is missing dest attribute');
}
const p = path.resolve(item.src);
console.log('** Importing ' + p, ' to ', outputPath);
cpx.copySync(item.src, path.join(outputPath, item.dest), { includeEmptyDirs: item.includeEmptyDirs });
}
//fix url paths in css files - make them flat
if ((item.src && item.src.endsWith('.css')) || (item.endsWith && item.endsWith('.css'))) {
const destPath = item.dest ? item.dest : (path.basename(item));
let cssFile = fs.readFileSync(path.join(outputPath, destPath), 'utf8');
const regex = /(?:url\(['"](.*?)\))/gm;
let m;
while ((m = regex.exec(cssFile)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
// The result can be accessed through the `m`-variable.
m.forEach((match, groupIndex) => {
if (groupIndex == 1) {
const fileName = path.basename(match);
if (match && match.indexOf('data:') < 0) {
cssFile = cssFile.replace(match, './' + fileName);
}
}
});
}
console.log('writing', path.join(outputPath, destPath))
fs.writeFileSync(path.join(outputPath, destPath), cssFile, 'utf8');
}
//copy file to all locale desitnation folders
const src = path.resolve(path.join(outputPath, item.dest ? item.dest : (path.basename(item))));
for (const localeCode in languages) {
if (localeCode != 'en') {
const dest = path.resolve(path.join(outputPath.replace('/en', '/' + localeCode)));
cpx.copySync(src, dest, { includeEmptyDirs: item.includeEmptyDirs });
}
}
});
console.log('Importing: DONE');
console.log('');
}
var htmlHeaderTS = fs.readFileSync(path.resolve(rootPath, `../node_modules/dynamicsmobile/templates/${isMobileApp ? 'mobile-app' : 'backend-app'}/dms/templates/viewheader-ts.html`), 'utf8');
var htmlContentTS = fs.readFileSync(path.resolve(rootPath, `../node_modules/dynamicsmobile/templates/${isMobileApp ? 'mobile-app' : 'backend-app'}/dms/templates/view-ts.html`), 'utf8');
var htmlFooterTS = fs.readFileSync(path.resolve(rootPath, `../node_modules/dynamicsmobile/templates/${isMobileApp ? 'mobile-app' : 'backend-app'}/dms/templates/viewfooter-ts.html`), 'utf8');
this.generateTaskFiles(taskFiles, compilationProfileName, app, actualExtRootDir, actualRootDir, actualTmpRootDir, rootPath, htmlHeaderTS, htmlContentTS, htmlFooterTS, inputEntries,taskBatch);
this.generateReleaseNotes(app, outputPath);
console.log(chalk.white.bgGreen(' DMS '), 'Dynamics Mobile Application Code Generator work completed');
console.log('')
console.log(chalk.white.bgGreen(' DMS '),'Compiling ', Object.getOwnPropertyNames(inputEntries).length, 'tasks, batch:',taskBatch?taskBatch.replace(',',' of '):'all');
console.log(inputEntries)
return inputEntries;
}
writeLozalizedTaskHtml(app, taskContent, taskHtml) {
//html language generation
//get list of language codes from the locale file names
const localeCodes = app.dms.locales;
//write the html once for each language in a seprate folder
localeCodes.forEach(localeCode => {
let actualPath = path.resolve(outputPath, taskContent.id + ".html");
if (outputPath.indexOf('/') >= 0) {
actualPath = actualPath.replace(/\\en\\/gi, `/${localeCode}/`);
}
else {
actualPath = actualPath.replace(/\/en\//gi, `\\${localeCode}\\`);
}
let localizedTaskHtml = taskHtml;
//translation of HTML files
if (languages && languages[localeCode]) {
for (const key in languages[localeCode]) {
{
var regEx = new RegExp(`{#${key}#}`, 'gi');
localizedTaskHtml = localizedTaskHtml.replace(regEx, (languages[localeCode][key] ? languages[localeCode][key].replace(/</g, '<').replace(/>/g, '>').replace(/\//g, '/').replace(/\n/g, ' ').replace(/\t/g, ' ') : '').replace(/'/g, ''').replace(/"/g, '"'));
}
}
}
if (!fs.existsSync(path.dirname(actualPath))) {
fs.mkdirSync(path.dirname(actualPath), { recursive: true });
}
if (isMobileApp) {
if (app.dms.noFramework7CssLink == true) {
//old Android webview prior to 2019 does not support external css files
localizedTaskHtml = localizedTaskHtml.replace('$f7styles$', '')
}
else {
if (actualPath.indexOf('/ar/') > 0 || actualPath.indexOf('/he/') > 0) {
//add rtl class
localizedTaskHtml = localizedTaskHtml.replace('<html', '<html dir="rtl" ');
localizedTaskHtml = localizedTaskHtml.replace('$f7styles$', '<link rel="stylesheet" href="./framework7-bundle-rtl.min.css" >');
}
else {
localizedTaskHtml = localizedTaskHtml.replace('<html', '<html dir="ltr" ');
localizedTaskHtml = localizedTaskHtml.replace('$f7styles$', '<link rel="stylesheet" href="./framework7-bundle.min.css" >');
}
}
}
else {
if (actualPath.indexOf('/ar/') > 0 || actualPath.indexOf('/he/') > 0) {
localizedTaskHtml = localizedTaskHtml.replace('<html', '<html dir="rtl"');
}
else {
localizedTaskHtml = localizedTaskHtml.replace('<html', '<html dir="ltr"');
}
localizedTaskHtml = localizedTaskHtml.replace('$f7styles$', '')
}
//automatic css import
const cssListToImport = [];
if (app.dms && app.dms.import && app.dms.import.length > 0) {
app.dms.import.forEach(function (item) {
if (typeof (item) == 'string') {
if (item.endsWith('.css')) {
if (localizedTaskHtml.indexOf(path.basename(item)) < 0 && path.basename(item).toLowerCase() != 'framework7-bundle.min.css' && path.basename(item).toLowerCase() != 'framework7-bundle-rtl.min.css') {
cssListToImport.push(path.basename(item));
}
}
}
else if (typeof (item) == 'object') {
if (item.src && item.src.endsWith('.css')) {
if (localizedTaskHtml.indexOf(path.basename(item.src)) < 0 && path.basename(item.src).toLowerCase() != 'framework7-bundle.min.css' && path.basename(item.src).toLowerCase() != 'framework7-bundle-rtl.min.css') {
cssListToImport.push(path.basename(item.src));
}
}
}
});
}
const cssImports = cssListToImport.map(css => `<link rel="stylesheet" href="./${css}">`).join('\n');
localizedTaskHtml = localizedTaskHtml.replace('$morestyles$', cssImports);
fs.writeFileSync(actualPath, localizedTaskHtml);
});
}
generateTaskFiles(taskFiles, compilationProfileName, app, actualExtRootDir, actualRootDir, actualTmpRootDir, rootPath, htmlHeaderTS, htmlContentTS, htmlFooterTS, inputEntries,taskBatch) {
const me = this;
let batchSize = taskFiles.length
const batchSizeArgs = taskBatch;
let batchPageNumber = 0;
let batchMaxPages = 0;
if (batchSizeArgs) {
const batchConfigPart = batchSizeArgs.split(',');
if (batchConfigPart.length < 2) {
throw new Error('DMS App compile error 1002: taskBatch argument must be in format taskBatch=[batchPage],[batchMaxPages]');
}
batchPageNumber = parseInt(batchConfigPart[0]);
batchMaxPages = parseInt(batchConfigPart[1]);
if (isNaN(batchPageNumber) || isNaN(batchMaxPages)) {
throw new Error('DMS App compile error 1002: taskBatch argument must be in format taskBatch=[batchPage],[batchMaxPages]');
}
if (batchPageNumber > batchMaxPages) {
throw new Error('DMS App compile error 1002: taskBatch.batchPage must be less than taskBatch.batchMaxPages');
}
if (batchPageNumber <= 0) {
throw new Error('DMS App compile error 1002: taskBatch.batchPage must be greater than 0');
}
if (batchMaxPages < 1) {
throw new Error('DMS App compile error 1002: taskBatch.batchMaxPages must be greater than 0');
}
}
taskFiles.forEach(function (f, taskIndex) {
const p = f.indexOf('.task.json');
const taskId = path.basename(f).split('.')[0];
let taskMusBeCompiled = true;
if ((compilationProfileName && compilationProfileName.toLowerCase().indexOf('dev') == 0) && app.dms && app.dms.devProfile && app.dms.devProfile.tasks && app.dms.devProfile.tasks.length > 0) {
taskMusBeCompiled = false;
if (app.dms.devProfile.tasks.indexOf(taskId) >= 0) {
taskMusBeCompiled = true;
}
else {
console.log(chalk.bold.magenta(`devProfile: Task ${taskId} was not compiled (check package.json/dms/devProfile)`));
}
}
if (batchMaxPages > 0 && batchPageNumber > 0) {
const tasksPerPage = Math.ceil(taskFiles.length / batchMaxPages); // Calculate tasks per page
const pageStart = (batchPageNumber - 1) * tasksPerPage; // Start index for the current batch
const pageEnd = pageStart + tasksPerPage - 1; // End index for the current batch
if (taskIndex >= pageStart && taskIndex <= pageEnd) {
taskMusBeCompiled = true;
//console.log(chalk.bold.green(`Task ${taskId} was compiled (taskIndex: ${taskIndex}, tasksPerPage: ${tasksPerPage}, batchPageNumber: ${batchPageNumber}, batchMaxPages: ${batchMaxPages})`));
} else {
taskMusBeCompiled = false;
//console.log(chalk.bold.red(`Task ${taskId} was not compiled (taskIndex: ${taskIndex}, tasksPerPage: ${tasksPerPage}, batchPageNumber: ${batchPageNumber}, batchMaxPages: ${batchMaxPages})`));
}
}
if (p > 0 && p == f.length - '.task.json'.length && taskMusBeCompiled) {
var isTypeScriptView = false;
var taskHtml = null;
var taskLevelControls = '';
var tsViewsArray = [];
var viewScriptReferences = [];
var tsImports = [];
var taskContent;
var actualRootDirWithExt;
if (fs.existsSync(path.join(actualExtRootDir, f))) {
actualRootDirWithExt = actualExtRootDir;
}
else
if (fs.existsSync(path.join(actualRootDir, f))) {
actualRootDirWithExt = actualRootDir;
}
else {
actualRootDirWithExt = actualTmpRootDir;
}
try {
taskContent = JSON.parse(fs.readFileSync(path.join(actualRootDirWithExt, f), 'utf8'));
}
catch (err) {
throw `Task ${f} has invalid json content!>${err}`;
}
//replace services
if (fs.existsSync(path.resolve(rootPath, '../ext/replace-services.ts')))
tsImports.push(`import {replaceServices} from '../../../../../../ext/replace-services';replaceServices();`);
taskContent.steps.forEach(function (step) {
var stepViewPath = path.resolve('./ext/', step.view);
var extOverrideExists = true;
if (!fs.existsSync(stepViewPath + '.ts')) {
stepViewPath = path.resolve(rootPath, step.view);
extOverrideExists = false;
}
if (fs.existsSync(stepViewPath + ".ts")) {
var __routes = [];
__routes.push({
name: step.id,
path: `/${step.id}/`,
pageName: step.id
});
//add the actual routes
var i = 0;
step.routes.forEach(r => {
//validate target
if (r.target && r.target.indexOf('task:') < 0) {
const targetSteps = taskContent.steps.filter(s => s.id == r.target);
if ((!targetSteps || targetSteps.length == 0) && r.target != '$back') {
console.log('');
console.log(chalk.white.bgRed(' DMS ERROR '), chalk.white(`Route '${r.id}' in task: '${taskContent.id}, step: ${step.id}' points to non existing step '${r.target}'`));
console.log('');
process.exit(1);
}
}
if (r.target && r.target.indexOf('task:') == 0) {
const targetTasks = taskFiles.filter(t => path.basename(t).split('.')[0] == r.target.split(':')[1].trim());
if (!targetTasks || targetTasks.length == 0) {
console.log('');
console.log(chalk.white.bgRed(' DMS ERROR '), chalk.white(`Route '${r.id}' in task:'${taskContent.id}, step: ${step.id}' points to non existing task '${r.target}'`));
console.log('');
process.exit(1);
}
}
__routes.push(
{
name: step.id + step.routes[i].id,
path: `/${step.id}${step.routes[i].id}/`,
pageName: step.routes[i].target,
validate: step.routes[i].validate,
preserveContext: step.routes[i].preserveContext
}
);
i++;
});
var viewClassName = step.view.replace(/^.*[\\\/]/, '');
isTypeScriptView = true;
tsImports.push(`import {${viewClassName}} from '../../../../../../${extOverrideExists ? 'ext' : 'src'}/${step.view}'; `);
//used to match view-id against actual view class, as the class names are uglified during production release
tsImports.push(`${viewClassName}["__className"]='${viewClassName}'; `);
tsImports.push(`${viewClassName}["__stepId"] = '${step.id}'`);
tsImports.push(`${viewClassName}["__routes"]='${JSON.stringify(__routes)}'; `);
tsViewsArray.push(`${viewClassName}`);
viewScriptReferences[0] = `<script crossorigin="anonymous" src="./framework.bundle.js"></script><script crossorigin="anonymous" src="./${taskContent.id}Task.js"></script>`;
if (taskHtml == null) {
taskHtml = (isTypeScriptView ? htmlHeaderTS : htmlHeaderJS);
}
if (taskHtml.indexOf(`</head>`) > 0 && (fs.existsSync('./src/manifest.json') || fs.existsSync('./ext/manifest.json'))) {
taskHtml = taskHtml.replace(`</head>`, `${'<link href="./manifest.json" rel="manifest">'}</head>`);
}
var stepHtml;
var p = path.resolve('./ext', step.view + '.html');
if (fs.existsSync(p)) {
stepHtml = fs.readFileSync(p, 'utf8');
}
else {
stepHtml = fs.readFileSync(stepViewPath + '.html', 'utf8');
}
var htmlContent = (isTypeScriptView ? htmlContentTS : htmlContentJS);
var viewRawHtml = htmlContent.replace('{#content#}', stepHtml).replace(/{#id#}/g, step.id).replace(/{#stepid#}/g, step.id).replace(/{#taskid#}/, taskContent.id).replace(/{#moduleid#}/g, app.name.toUpperCase()).replace(/{#classname#}/g, viewClassName);
//process components
var { viewHtml, components, componentImportTs, taskLevelHtml } = DmsAppWebPackPlugin.processComponents(taskContent.id, step.id, viewRawHtml);
componentImportTs.forEach(element => {
if (tsImports.indexOf(element) < 0)
tsImports.push(element);
});
tsImports.push(`${viewClassName}["__components"]=[${components.join(",")}]; `);
taskHtml += viewHtml;
taskLevelControls += taskLevelHtml;
}
else {
console.log(chalk.white.bgRed(' DMS ERROR '), chalk.white(`Step ${step.id} in task ${taskContent.id} points to non existing view ${step.view}`));
}
});
const htmlFooterJS = '';
taskHtml += (isTypeScriptView ? htmlFooterTS : htmlFooterJS);
taskHtml = taskHtml.replace('{#scripts#}', viewScriptReferences.join(''));
taskHtml = taskHtml.replace(/{#CACHECONTROL#}/g, '');
taskHtml = taskHtml.replace('<!--headercomponents-->', taskLevelControls);
me.writeLozalizedTaskHtml(app, taskContent, taskHtml);
if (isTypeScriptView) {
let appImportString = '';
let appInstanceCode = '';
if (fs.existsSync(path.resolve(outputPath, `../../../../../../ext/app.ts`))) {
appImportString = `import {App} from '../../../../../../ext/app';`;
appInstanceCode = `new App()`;
}
else if (fs.existsSync(path.resolve(outputPath, `../../../../../../src/app.ts`))) {
appImportString = `import {App} from '../../../../../../src/app';`;
appInstanceCode = `new App()`;
}
else {
appImportString = `import {Application} from '@dms';`;
appInstanceCode = `new Application()`;
}
let oldContentHash;
if (fs.existsSync(path.resolve(outputPath, taskContent.id + "Task.ts"))) {
const oldCnt = fs.readFileSync(path.resolve(outputPath, taskContent.id + "Task.ts"), 'utf8');
//calculate hash from the string file content
oldContentHash = crypto.createHash('sha256').update(oldCnt).digest("hex");
}
const newContent = `import {${isMobileApp ? 'MobileDmsTask' : 'BackendDmsTask'}} from '@dms'; ${appImportString} ${tsImports.join('; ')} ; var task = new ${isMobileApp ? 'MobileDmsTask' : 'BackendDmsTask'}('${app.name.toUpperCase()}','${taskContent.id}',${appInstanceCode},${taskContent.allowPublicAccess == true ? true : false},[${tsViewsArray.join(',')}]);task.run();`;
const newContentHash = crypto.createHash('sha256').update(newContent).digest("hex");
//if (!oldContentHash || oldContentHash != newContentHash) {
fs.writeFileSync(path.resolve(outputPath, taskContent.id + "Task.ts"), newContent);
//}
//else {
//console.log('skipping ', path.resolve(outputPath, taskContent.id + "Task.ts"));
//}
inputEntries[`${taskContent.id}Task`] = path.resolve(outputPath, taskContent.id + "Task.ts");
}
}
});
}
apply(compiler) {
return;
compiler.hooks.emit.tap('DmsAppWebPackPlugin', (compilation) => {
// Explore each chunk (build output):
compilation.chunks.forEach(function (chunk) {
// Explore each module within the chunk (built inputs):
// chunk.forEachModule(function (module) {
// // Explore each source file path that was included into the module:
// module.fileDependencies.forEach(function (filepath) {
// // we've learned a lot about the source structure now...
// });
// });
// Replace 'self' in output files
//chunk.files.forEach(function (filename) {
// var sourceCode = compilation.assets[filename].source();
// if (filename.indexOf('.js') > 0) {
// var regex = new RegExp(/{#\w+#}/gi);
// let m;
// while ((m = regex.exec(sourceCode)) !== null) {
// // This is necessary to avoid infinite loops with zero-width matches
// if (m.index === regex.lastIndex) {
// regex.lastIndex++;
// }
// // The result can be accessed through the `m`-variable.
// m.forEach((match, groupIndex) => {
// //console.log(`Found match, group ${groupIndex}: ${match}`);
// const safeText = languages[languageCode][match.replace(/{#|#}/gi, '')]
// console.log(safeText)
// sourceCode = sourceCode.replace(regex, safeText);
// });
// }
// compilation.assets[filename] = {
// source: () => {
// return sourceCode;
// },
// size: () => {
// return Buffer.byteLength(sourceCode, 'utf8');
// },
// };
// }
//})
});
});
try {
const jsframework = fs.readFileSync(path.resolve('./.bin/user/apparea/SANDBOX/APP/en/framework.bundle.js'));
fs.writeFileSync(path.resolve('./.bin/framework.bundle.js'), jsframework, 'utf8');
const js = fs.readFileSync(path.resolve('./.bin/user/apparea/SANDBOX/APP/en/LoginSuccessTask.js'));
fs.writeFileSync(path.resolve('./.bin/LoginSuccessTask.js'), js, 'utf8');
//fs.unlinkSync(path.resolve('./.bin/user/apparea/SANDBOX/APP/en/LoginSuccessTask.js'));
}
catch (err) {
}
try {
const html = fs.readFileSync(path.resolve('./.bin/user/apparea/SANDBOX/APP/en/LoginSuccess.html'));
fs.writeFileSync(path.resolve('./.bin/LoginSuccess.html'), html, 'utf8');
//fs.unlinkSync(path.resolve('./.bin/user/apparea/SANDBOX/APP/en/LoginSuccess.html'));
}
catch (err) {
}
try {
//fs.unlinkSync(path.resolve('./.bin/user/apparea/SANDBOX/APP/en/LoginSuccessTask.ts'));
}
catch (err) {
}
};
}
module.exports = DmsAppWebPackPlugin;