UNPKG

flexmonster-cli

Version:

CLI for Flexmonster Pivot Table & Charts installation

777 lines (735 loc) 29.9 kB
import axios from 'axios'; import fs, { watch } from 'fs'; import { sync } from 'rimraf'; import decompress from 'decompress'; import chalk from 'chalk'; import { tempDirURL } from '../cli.js'; import { isWindows, isLinux, isMacOS, copyFolderSync, getArchiveNameFromURL, getOSString } from '../utils.js' import { exec as execNonPromise, spawn } from 'child_process'; import path from 'path'; import { promisify } from "util"; import sudo from '@vscode/sudo-prompt'; const exec = promisify(execNonPromise); export const NO_FRAMEWORK = 'no framework'; export async function downloadArchive(url, archiveName, task) { try { const response = await axios({ url: url, method: 'GET', responseType: 'stream' }); if (task) { const title = task.title; const total = parseInt(response.headers["content-length"]); let loaded = 0; response.data.on("data", (buffer) => { loaded += buffer.length; const percent = Math.round(loaded / total * 100); if (!isNaN(percent)) { task.title = `${title}${percent}%`; } }); } const file = fs.createWriteStream(archiveName); response.data.pipe(file); return new Promise((resolve, reject) => { file.on('finish', resolve); file.on('error', reject); }); } catch (error) { //console.error(error); return Promise.reject(new Error('Failed to download files')); } } export function deleteDownloadedArchive(archiveName) { fs.unlinkSync(archiveName); //delete } export function installAccelerator(fromURL, executable) { return new Promise((resolve, reject) => { try { var childProcess = spawn('cmd', ['/c', executable], { cwd: fromURL }); childProcess.stderr.on('data', (data) => { reject(new Error(data.toString())); }); childProcess.on('error', (error) => { reject(new Error(error.toString())); }); setTimeout(() => resolve(), 500); // ira: not sure why, but childProcess.stdout.on('data', ...) does not happen on my Windows /*childProcess.stdout.on('data', (data) => { resolve(data.toString()); });*/ } catch (error) { return reject(new Error('Failed to install the Accelerator')); } }); } export function installClientSideTool(toolObj) { return new Promise((resolve, reject) => { try { var command = isWindows() ? 'npm.cmd' : 'npm'; const childProcess = spawn(command, ['install', toolObj.module + '@latest', '--save-exact', '--loglevel=error'], { shell: true }); var stderror = ""; var stderrorTimer; childProcess.stderr.on('data', (data) => { data = data.toString(); if (data == "npm" || data.indexOf("WARN") >= 0 || data.indexOf("config global `--global`, `--local` are deprecated.") >= 0) { return; } stderror += data + "\n"; clearTimeout(stderrorTimer); stderrorTimer = setTimeout(() => reject(new Error(stderror)), 50); }); childProcess.on('error', (error) => { reject(new Error(error.toString())); }); if (isWindows(process.platform) || isLinux(process.platform)) { setTimeout(() => resolve(), 500); // ira: not sure why, but childProcess.stdout.on('data', ...) does not happen on my Windows } else { childProcess.stdout.on('data', (data) => { //resolve(data.toString()); }); } childProcess.on('close', (code) => { if (code > 0) { reject(new Error(`Installing ${toolObj.title} failed with code ${code}`)); } else { resolve(`${toolObj.title} installed successfully`); } }); } catch (error) { return reject(new Error(`Installing ${toolObj.title} failed with message: ${error.toString()}`)); } }); } export function isClientSideTool(tool) { return tool == 'flexmonster' || tool == 'js-flexmonster' || tool == 'ng-flexmonster' || tool == 'ngx-flexmonster' || tool == 'react-flexmonster' || tool == 'vue-flexmonster'; } export function isServerSideTool(tool) { return isFDS(tool) || isFA(tool); } export function isProjectBasedTool(tool) { return isCTB(tool); } export function isFDS(tool) { return tool === 'fds'; } export function isFDSExecutable(subtool) { return subtool === 'executable'; } export function isFA(tool) { return tool === 'accelerator'; } export function isCTB(tool) { return tool === 'theme-builder'; } export function isOSDependent(tool) { return isFDS(tool) || isFA(tool); } export function isForOS(toolList, tool) { const osObj = getServerToolByOS(toolList, tool, getOSString()); return !(isOSDependent(tool) && osObj === undefined); } export function getServerSideToolFolder(tool) { let folder = "/flexmonster-"; if (isFDS(tool)) { folder += "data-server"; } else { folder += tool; } return folder; } export function installAndRunServiceTool(tool, osObj, toolObj) { const projectFolder = getServerSideToolFolder(tool); var url = "." + projectFolder; return new Promise(async (resolve, reject) => { try { var childProcess; var executablePath; if (isWindows(process.platform)) { executablePath = osObj.serviceExecutable; childProcess = spawn('cmd', ['/c', executablePath], { cwd: url, detached: true, windowsHide: true, shell: true }); } else if (isMacOS(process.platform)) { url = path.resolve('') + projectFolder; try { await exec(`'./${osObj.serviceExecutable}'`, { cwd: url }); resolve(); } catch (e) { reject(e); } } else { executablePath = "sudo ./" + osObj.serviceExecutable; childProcess = spawn(executablePath, { cwd: url, shell: true, stdio: 'inherit' }); childProcess.on('close', (code) => { if (code > 0) { reject(new Error(`${toolObj.title} service failed with code ${code}`)); } else { resolve(`${toolObj.title} service installed successfully`); } }) } if (isWindows(process.platform)) { var stderror = ""; var stderrorTimer; childProcess.stderr.on('data', (data) => { stderror += data.toString() + "\n"; clearTimeout(stderrorTimer); stderrorTimer = setTimeout(() => reject(new Error(stderror)), 50); }); childProcess.on('error', (error) => { reject(new Error(error.toString())); }); childProcess.on('close', (code) => { if (code > 0) { reject(new Error(`${toolObj.title} service failed with code ${code}`)); } else { resolve(`${toolObj.title} service installed successfully`); } }) } } catch (error) { return reject(new Error(`Failed to install and start ${toolObj.title} as a service`)); } }); } async function macInstallAdminPanelDmg(dmgPath) { const { stdout } = await exec(`hdiutil attach "${dmgPath}" | grep Flexmonster`); const volume = stdout.substring(stdout.indexOf("/Volumes")).trim(); const appName = "Flexmonster Admin Panel.app"; const appsPath = "/Applications"; await exec(`open '${volume}'`); return new Promise((resolve, reject) => { const watcher = fs.watch(appsPath, async (eventType, filename) => { if (filename == appName) { watcher.close(); await hdiutilDetachVolume(volume); await exec(`open '${appsPath}/${appName}'`); resolve(); } }); }); } // retry 10 times during 10 seconds, because volume can be busy async function hdiutilDetachVolume(volume) { return new Promise(async (resolve, reject) => { let retries = 0; const detach = (volume) => { exec(`hdiutil detach '${volume}'`).then(() => { resolve(); }).catch(error => { retries++; if (retries == 10) { reject("Can't detach volume"); } setTimeout(() => detach(volume), 1000); }); } detach(volume); }); } export function installAndRunGUITool(tool, osObj, toolObj) { const projectFolder = getServerSideToolFolder(tool); var url = "." + projectFolder; return new Promise(async (resolve, reject) => { try { var childProcess; var executablePath; if (isWindows(process.platform)) { executablePath = osObj.guiExecutable; childProcess = spawn('cmd', ['/c', executablePath], { cwd: url, detached: true, windowsHide: true, shell: true }); } else if (isMacOS(process.platform)) { url = path.resolve('') + projectFolder; try { await macInstallAdminPanelDmg(`${url}/${osObj.guiExecutable}`); resolve(); } catch (e) { reject(e); } } else { executablePath = "./" + osObj.guiExecutable; childProcess = spawn(`exec ${executablePath} &exit`, { cwd: url, shell: true, detached: true }); setTimeout(() => resolve(), 2000); } if (isWindows(process.platform)) { var stderror = ""; var stderrorTimer; childProcess.stderr.on('data', (data) => { stderror += data.toString() + "\n"; clearTimeout(stderrorTimer); stderrorTimer = setTimeout(() => reject(new Error(stderror)), 50); }); childProcess.on('error', (error) => { reject(new Error(error.toString())); }); childProcess.on('close', (code) => { if (code > 0) { reject(new Error(`${toolObj.guiToolName} failed with code ${code}`)); } else { resolve(`${toolObj.guiToolName} installed and started successfully`); } }); } } catch (error) { console.error(error); return reject(new Error(`Failed to install and run ${toolObj.guiToolName}`)); } }); } export function uninstallServiceTool(osObj, toolObj) { return new Promise(async (resolve, reject) => { try { var childProcess; var executablePath; if (isWindows(process.platform)) { executablePath = osObj.serviceUninstaller; childProcess = spawn('cmd', ['/c', executablePath], { detached: true, windowsHide: true, shell: true }); } else if (isMacOS(process.platform)) { try { await exec(`'./${osObj.serviceUninstaller}'`); resolve(); } catch (e) { reject(e); } } else { executablePath = "sudo ./" + osObj.serviceUninstaller; childProcess = spawn(executablePath, { shell: true, stdio: 'inherit' }); childProcess.on('close', (code) => { if (code > 0) { reject(new Error(`Uninstalling ${toolObj.title} service failed with code ${code}`)); } else { resolve(`${toolObj.title} service uninstalled successfully`); } }) } if (isWindows(process.platform)) { var stderror = ""; var stderrorTimer; childProcess.stderr.on('data', (data) => { stderror += data.toString() + "\n"; clearTimeout(stderrorTimer); stderrorTimer = setTimeout(() => reject(new Error(stderror)), 50); }); childProcess.on('error', (error) => { reject(new Error(error.toString())); }); childProcess.on('close', (code) => { if (code > 0) { reject(new Error(`Uninstalling ${toolObj.title} service failed with code ${code}`)); } else { resolve(`${toolObj.title} service uninstalled successfully`); } }) } } catch (error) { return reject(new Error(`Failed to uninstall ${toolObj.title} service`)); } }); } export function updateAndRunServiceTool(osObj, toolObj) { return new Promise(async (resolve, reject) => { try { var childProcess; var executablePath; if (isWindows(process.platform)) { executablePath = osObj.serviceExecutable; childProcess = spawn('cmd', ['/c', executablePath], { detached: true, windowsHide: true, shell: true }); } else if (isMacOS(process.platform)) { try { await exec(`'./${osObj.serviceExecutable}'`); resolve(); } catch (e) { reject(e); } } else { executablePath = "sudo ./" + osObj.serviceExecutable; childProcess = spawn(executablePath, { shell: true, stdio: 'inherit' }); childProcess.on('close', (code) => { if (code > 0) { reject(new Error(`${toolObj.title} service failed with code ${code}`)); } else { resolve(`${toolObj.title} service updated successfully`); } }) } if (isWindows(process.platform)) { var stderror = ""; var stderrorTimer; childProcess.stderr.on('data', (data) => { stderror += data.toString() + "\n"; clearTimeout(stderrorTimer); stderrorTimer = setTimeout(() => reject(new Error(stderror)), 50); }); childProcess.on('error', (error) => { reject(new Error(error.toString())); }); childProcess.on('close', (code) => { if (code > 0) { reject(new Error(`${toolObj.title} service failed with code ${code}`)); } else { resolve(`${toolObj.title} service updated successfully`); } }); } } catch (error) { return reject(new Error(`Failed to update and start ${toolObj.title} as a service`)); } }); } export function updateAndRunGUITool(osObj, toolObj) { return new Promise(async (resolve, reject) => { try { var childProcess; var executablePath; if (isWindows(process.platform)) { executablePath = osObj.guiExecutable; childProcess = spawn('cmd', ['/c', executablePath], { detached: true, windowsHide: true, shell: true }); } else if (isMacOS(process.platform)) { await macInstallAdminPanelDmg(`./${osObj.guiExecutable}`); resolve(); } else { executablePath = "./" + osObj.guiExecutable; childProcess = spawn(`exec ${executablePath} &exit`, { shell: true, detached: true }); setTimeout(() => resolve(), 2000); } if (isWindows(process.platform)) { var stderror = ""; var stderrorTimer; childProcess.stderr.on('data', (data) => { stderror += data.toString() + "\n"; clearTimeout(stderrorTimer); stderrorTimer = setTimeout(() => reject(new Error(stderror)), 50); }); childProcess.on('error', (error) => { reject(new Error(error.toString())); }); childProcess.on('close', (code) => { if (code > 0) { reject(new Error(`${toolObj.guiToolName} failed with code ${code}`)); } else { resolve(`${toolObj.guiToolName} installed and started successfully`); } }); } } catch (error) { console.error(error); return reject(new Error(`Failed to install and run ${toolObj.guiToolName}`)); } }); } export function handleServiceInstallationProcess(tool, osObj, toolObj) { console.log(chalk.italic(`Installing ${toolObj.title} service (requires sudo privileges)`)); console.log(''); installAndRunServiceTool(tool, osObj, toolObj) .then( () => { console.log(chalk.green(`${toolObj.title} service installation complete`)); console.log(''); console.log(chalk.italic(`Starting ${toolObj.guiToolName} (the service GUI tool)`)); console.log(''); installAndRunGUITool(tool, osObj, toolObj) .then( () => { console.log(chalk.green(`${toolObj.guiToolName} is started`)); console.log(''); console.log('Press Ctrl+C here to enter new command (PS: This will not stop the started project)'); } ).catch(err => { console.log(chalk.red.bold(`${toolObj.guiToolName} failed to start`)); console.log(''); console.log('Check the %s folder', chalk.green.bold(getServerSideToolFolder(tool))); }) } ).catch(err => { console.log(chalk.red.bold(`${toolObj.title} service installation failed`)); console.log(err); console.log('Check the %s folder', chalk.green.bold(getServerSideToolFolder(tool))); }); } export function handleServiceUpdateProcess(tool, osObj, toolObj) { console.log(chalk.italic(`Installing latest ${toolObj.title} service version (requires sudo privileges)`)); console.log(''); updateAndRunServiceTool(osObj, toolObj) .then( () => { console.log(chalk.green(`${toolObj.title} service update complete`)); console.log(''); console.log(chalk.italic(`Starting ${toolObj.guiToolName} (the service GUI tool)`)); console.log(''); updateAndRunGUITool(osObj, toolObj) .then( () => { console.log(chalk.green(`${toolObj.guiToolName} is started`)); console.log(''); console.log('Press Ctrl+C here to enter new command (PS: This will not stop the started project)'); } ).catch(err => { console.log(chalk.red.bold(`${toolObj.guiToolName} failed to start`)); console.log(''); console.log('Check the %s folder', chalk.green.bold(getServerSideToolFolder(tool))); }) } ).catch(err => { console.log(chalk.red.bold(`${toolObj.title} service update failed`)); console.log(err); console.log('Check the %s folder', chalk.green.bold(getServerSideToolFolder(tool))); }); } export function checkIfServiceInstalled(osObj) { const command = osObj.serviceStatusCommand; return new Promise((resolve, reject) => { exec(command, function (error, stdout, stderr) { if (typeof stdout === "string" && stdout.length > 0 && stdout.indexOf("FAILED") < 0) { resolve(true); } if (error || stderr) { resolve(false); } }); }); } export function stopService(toolObj, osObj) { const command = osObj.serviceStopCommand; return new Promise(async (resolve, reject) => { if (isWindows(process.platform)) { sudo.exec(command, { name: toolObj.title }, function (error, stdout, stderr) { if (error || stderr) { resolve(false); } else { resolve(true) } }); } else { try { const { stderr } = await exec(command); if (typeof stderr === "string" && stderr.length > 0) { return resolve(false); } resolve(true); } catch (e) { resolve(false); } } }); } export async function closeGUITool(osObj) { const command = osObj.guiToolStopCommand; try { const { stderr } = await exec(command); if (typeof stderr === "string" && stderr.length > 0) { return false; } } catch (e) { return false; } return true; } /************************* * Work with tool lists - for add and update commands *************************/ export function getTool(list, tool) { for (let i = 0; i < list.length; i++) { if (list[i].value == tool || list[i].name == tool) { return list[i]; } } return undefined; } export function getSubtool(list, subtool, tool) { for (let i = 0; i < list.length; i++) { if (list[i].value === tool || list[i].name == tool) { if (list[i].subtool === subtool) return list[i]; else return undefined; } } return undefined; } function hasSubtool(list, subtool, tool) { return getSubtool(list, subtool, tool) != undefined; } export function isValidSubtool(list, subtool, tool) { if (!isValidTool(list, tool)) return false; if (subtool == undefined) return false; tool = tool.toLowerCase(); subtool = subtool.toLowerCase(); return hasSubtool(list, subtool, tool); } function hasTool(list, tool) { return getTool(list, tool) != undefined; } export function isValidTool(list, tool) { if (tool == undefined) return false; tool = tool.toLowerCase(); return hasTool(list, tool); } function getServerToolOSList(list, tool) { const toolObj = getTool(list, tool); return toolObj.os; } export function getServerToolByOS(list, tool, os) { const osList = getServerToolOSList(list, tool); if (osList != undefined) { for (let i = 0; i < osList.length; i++) { if (osList[i].name === os) { return osList[i]; } } } return undefined; } /************************* * Work with Projects - for create and add commands *************************/ export function getProjectString(projectObj, versionObj, configurationObj) { const projectTitle = projectObj.title; const versionTitle = versionObj !== undefined ? versionObj.title : undefined; const configurationTitle = configurationObj !== undefined ? configurationObj.title : undefined; let str = ""; if (projectTitle !== undefined && projectObj.value != NO_FRAMEWORK) { str += "framework: " + projectTitle; } if (versionTitle !== undefined) { if (str != "") { str += ", "; } str += "version: " + versionTitle; } if (configurationTitle !== undefined) { if (str != "") { str += ", "; } str += "configuration: " + configurationTitle; } str = "(" + str + ")"; return str; } export function getProjectProperty(projectObj, versionObj, configurationObj, propName) { const value = configurationObj && configurationObj[propName] !== undefined ? configurationObj[propName] : (versionObj && versionObj[propName] !== undefined ? versionObj[propName] : (projectObj && projectObj[propName] !== undefined ? projectObj[propName] : "")); return value; } export function getProjectURL(projectObj, versionObj, configurationObj) { return getProjectProperty(projectObj, versionObj, configurationObj, 'url'); } export function getProjectArchiveName(projectObj, versionObj, configurationObj) { let name = getProjectProperty(projectObj, versionObj, configurationObj, 'archiveName'); if (name === "") { // ira: just in case we forget to define archiveName for a project or a configuration const url = getProjectURL(projectObj, versionObj, configurationObj); name = getArchiveNameFromURL(url); } return name; } export async function downloadProject(projectObj, versionObj, configurationObj, task) { const url = getProjectURL(projectObj, versionObj, configurationObj); const archiveName = getProjectArchiveName(projectObj, versionObj, configurationObj); return await downloadArchive(url, archiveName, task); } export function getProjectFolder(projectObj, versionObj, configurationObj, webpack) { let folder = "/flexmonster"; if (projectObj.value != NO_FRAMEWORK) folder += "-" + projectObj.value; if (versionObj && versionObj.value != undefined) folder += "-" + versionObj.value; if (configurationObj && configurationObj.value != undefined) folder += "-" + configurationObj.value; if (webpack) folder += "-webpack"; folder += "-project"; return folder; } export function unpackProject(projectObj, versionObj, configurationObj, webpack) { //create temp directory for unpacking if (!fs.existsSync(tempDirURL)) { fs.mkdirSync(tempDirURL); } try { const archiveName = getProjectArchiveName(projectObj, versionObj, configurationObj); let file = decompress(archiveName, tempDirURL); return new Promise((resolve, reject) => { file.then((files) => { const projectFolderURL = "." + getProjectFolder(projectObj, versionObj, configurationObj, webpack); try { if (fs.existsSync(projectFolderURL)) { sync(projectFolderURL); } fs.mkdirSync(projectFolderURL); let dir = getProjectProperty(projectObj, versionObj, configurationObj, 'dir'); if (dir.length > 0) { //todo: it would be better to check if this folder exists copyFolderSync(tempDirURL + dir, projectFolderURL); } else { copyFolderSync(tempDirURL, projectFolderURL); } sync(tempDirURL); resolve(); } catch (error) { if (fs.existsSync(projectFolderURL)) { sync(projectFolderURL); } sync(tempDirURL); reject(error); } }); file.catch((error) => { sync(tempDirURL); reject(error); }); }); } catch (error) { sync(tempDirURL); return Promise.reject(new Error('Failed to unpack')); } }