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

957 lines (831 loc) 37.7 kB
/** * 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, '&lt;').replace(/>/g, '&gt;').replace(/\//g, '&#47;').replace(/\n/g, '&#13;').replace(/\t/g, ' ').replace(/'/g, '&#39;').replace(/"/g, '&#34;'); 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, '&lt;').replace(/>/g, '&gt;').replace(/\//g, '&#47;').replace(/\n/g, '&#13;').replace(/\t/g, ' ') : '').replace(/'/g, '&#39;').replace(/"/g, '&#34;')); } } } 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;