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

353 lines (303 loc) 12.1 kB
/*** Dynamics Mobile www.dynamicsmobile.com 2025 All rights reserved publishing of mobile or backend app */ const fetch = require('node-fetch'); const fs = require('fs'); const os = require('os'); const path = require('path'); const chalk = require('chalk'); const { zip } = require('zip-a-folder'); var baseAPIUrl = "https://api.portal.dynamicsmobile.com"; var binPath = './.bin/user/apparea/SANDBOX/APP'; let appPackage = null; async function uploadAppZip(filePath, app, profile) { return new Promise(async function (resolve, reject) { const url = `${baseAPIUrl}/studio/${app.name.toUpperCase()}/appupload2`; try { const headers = { 'Authorization': profile.user ? profile.token : undefined, 'x-api-key': profile.user ? undefined : profile.token }; const response = await fetch(url, { method: "GET", headers: headers }); if (!response.ok) { console.log(chalk.red(`DMS ERROR 102: Dynamics Mobile Cloud returned error status ${response.status}>> ${await response.text()}. Use "dms login" command from command line, first`)); console.log(); reject(); return; } var data = await response.json(); await uploadZip(filePath, data.url); resolve(); } catch (err) { console.log(chalk.red("DMS ERROR 101: "), err); console.log(); reject(); } }); } async function uploadDemoDataZip(filePath, app, profile) { return new Promise(async function (resolve, reject) { var url; if (profile.user) url = `${baseAPIUrl}/studio/${app.name.toUpperCase()}/appupload?demoData=true`; else url = `${baseAPIUrl}/studio/${app.name.toUpperCase()}/appupload2?demoData=true`; try { const headers = { 'Authorization': profile.user ? profile.token : undefined, 'x-api-key': profile.user ? undefined : profile.token }; const response = await fetch(url, { method: "GET", headers: headers }); if (!response.ok) { console.log(chalk.red(`DMS ERROR 102: Dynamics Mobile Cloud returned error status ${response.status}>> ${await response.text()}. Use "dms login" command from command line, first`)); console.log(); reject(); return; } var data = await response.json(); await uploadZip(filePath, data.url); resolve(); } catch (err) { console.log(chalk.red("DMS ERROR 101: "), err); console.log(); reject(); } }); } function copyOtherFiles(appCode) { return new Promise(function (resolve, reject) { try { //generate app map doc automatically process.argv.push('--silent') require('./dms-bo-map'); //copy app map doc to .bin var content = fs.readFileSync('./.bin/dms-bo.html', 'utf8'); for (let i = 0; i < appPackage.dms.locales.length; i++) { fs.writeFileSync(path.join(binPath, appPackage.dms.locales[i], 'dms-bo.html'), content); } content = fs.readFileSync('./.bin/dms-bo.json', 'utf8'); for (let i = 0; i < appPackage.dms.locales.length; i++) { fs.writeFileSync(path.join(binPath, appPackage.dms.locales[i], 'dms-bo.json'), content); } //generate metadata file for dms agent const md = JSON.parse(content); const newMd = {}; newMd[appCode] = md; for (let i = 0; i < appPackage.dms.locales.length; i++) { fs.writeFileSync(path.join(binPath, appPackage.dms.locales[i], 'metadata.json'), JSON.stringify(newMd)); } content = JSON.parse(fs.readFileSync('./package.json', 'utf8')); var app = { appCode: content.name.toUpperCase(), version: content.version, appType: content.dms.appType, defaultTask: "Home", system: (content.dms.system == true ? true : false) }; for (let i = 0; i < appPackage.dms.locales.length; i++) { fs.writeFileSync(path.join(binPath, appPackage.dms.locales[i], 'app.json'), JSON.stringify(app)); } content = fs.readFileSync(path.join(binPath, 'en', 'Home.html'), 'utf8'); for (let i = 0; i < appPackage.dms.locales.length; i++) { fs.writeFileSync(path.join(binPath, appPackage.dms.locales[i], 'modules.html'), content); fs.writeFileSync(path.join(binPath, appPackage.dms.locales[i], appCode + '.html'), content); } resolve(); } catch (err) { reject(err); } }); } function deleteNonNeededFiles(folder) { return new Promise(function (resolve, reject) { try { var files = fs.readdirSync(folder); files.forEach(function (file) { if (file.indexOf('.ts') > 0) { fs.unlinkSync(path.join(folder, file)); } }); resolve(); } catch (err) { reject(err); } }); } async function publishApp(appCode, version, auth, manifest, appType, user, language) { return new Promise(async function (resolve, reject) { try { var url = baseAPIUrl + `/studio/${appCode}/studio2marketplace?version=${version}&appType=${appType}` const headers = { 'Content-Type': 'application/json', 'Authorization': user ? auth : undefined, 'x-api-key': user ? undefined : auth }; const options = { method: "POST", headers: headers, body: JSON.stringify({ appDef: { appCode: appCode, version: version, appType: appType, language: language }, manifest: manifest }) }; const response = await fetch(url, options); if (!response.ok) { const errorText = await response.text(); return reject(errorText); } resolve(); } catch (err) { console.log(chalk.red('DMS ERROR: '), err); resolve(); } }); } async function uploadZip(localZipPath, url) { return new Promise(async function (resolve, reject) { try { const stats = fs.statSync(path.resolve(localZipPath)); const fileSizeInBytes = stats.size; const fileStream = fs.createReadStream(path.resolve(localZipPath)); const headers = { 'Content-Type': 'application/zip', 'Content-Length': fileSizeInBytes }; const response = await fetch(url, { method: "PUT", headers: headers, body: fileStream }); if (!response.ok) { const errorText = await response.text(); return reject(errorText); } resolve(); } catch (err) { reject(err); } }); } async function executeAppPublishing() { 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; } if (!fs.existsSync('./package.json')) { console.log(chalk.red('DMS ERROR: File package.json does not exists. Use "dms init" command from command line, first')); console.log(); process.exit(1); return; } if (!fs.existsSync('./appmanifest.json')) { console.log(chalk.red('DMS ERROR: File appmanifest.json does not exists.Can not publish app to marketplace.')); console.log(); process.exit(1); return; } appPackage = fs.readFileSync('./package.json', 'utf8'); try { appPackage = JSON.parse(appPackage); } catch (err) { console.log(chalk.red('DMS ERROR: File package.json has invalid format!')); console.log(); process.exit(1); return; } const app = appPackage; var manifest = fs.readFileSync('./appmanifest.json', 'utf8'); try { manifest = JSON.parse(manifest); } catch (err) { console.log(chalk.red('DMS ERROR: File appmanifest.json has invalid format!')); console.log(); process.exit(1); return; } if (app.dms.appArea != profile.appArea) { console.log(); console.log(chalk.white.bgRed(' ERROR '), `Wrong Application Area!`); console.log(`apparea in package.json/dms/apparea`, chalk.bgGreen(` ${app.dms.appArea} `), `is not the same as the current profile apparea`, chalk.bgGreen(` ${profile.appArea} `)); console.log(); process.exit(1); return; } await copyOtherFiles(app.name.toUpperCase()); console.log(chalk.blue(`Dynamics Mobile is releasing app to marketplace: ${app.name.toUpperCase()}, v.${app.version} area: ${profile.appArea} ...`)); var localAppZipPath = `./.dms/${app.name.toUpperCase()}.zip`; var localDemoDataZipPath = `./.dms/${app.name.toUpperCase()}_demoData.zip`; if (!fs.existsSync('./.dms')) fs.mkdirSync('./.dms'); if (!appPackage.dms.locales || appPackage.dms.locales.length == 0) { throw new Error('dms.locales are not defined in package.json'); } console.log('deleteting non needed files...'); await deleteNonNeededFiles(binPath); for (let i = 0; i < appPackage.dms.locales.length; i++) { let locale = appPackage.dms.locales[i]; await deleteNonNeededFiles(path.join(binPath, locale)); } console.log('Preparing app artefacts...'); await zip(binPath, localAppZipPath); //upload demo data, if exists var localDemoDataPath; if (fs.existsSync('./ext/Data/init.xml')) localDemoDataPath = './ext/Data'; else if (fs.existsSync('./src/Data/init.xml')) localDemoDataPath = './src/Data'; if (fs.existsSync(localDemoDataZipPath)) fs.unlinkSync(localDemoDataZipPath); if (localDemoDataPath) await zip(localDemoDataPath, localDemoDataZipPath); console.log('Starting app artefacts uploading...'); try { await uploadAppZip(localAppZipPath, app, profile); if (localDemoDataZipPath && fs.existsSync(localDemoDataZipPath)) await uploadDemoDataZip(localDemoDataZipPath, app, profile); console.log(`App artefacts uploaded`); console.log('Releasing app to marketplace...'); let actualVersion = app.version; await publishApp(app.name.toUpperCase(), actualVersion, profile.token, manifest, app.dms.appType, profile.user, app.dms.language); console.log(chalk.green(`App ${app.name.toUpperCase()}, v.${actualVersion} was released to market place. Review is pending`)); } catch (err) { console.log(chalk.red('ERROR: '), err); process.exit(1); return; } } executeAppPublishing();