flexmonster-cli
Version:
CLI for Flexmonster Pivot Table & Charts installation
777 lines (735 loc) • 29.9 kB
JavaScript
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'));
}
}