flexmonster-cli
Version:
CLI for Flexmonster Pivot Table & Charts installation
466 lines (449 loc) • 18.7 kB
JavaScript
import chalk from 'chalk';
import escExit from 'esc-exit';
import fs from 'fs';
import inquirer from 'inquirer';
import Listr from 'listr';
import { sync } from 'rimraf';
import decompress from 'decompress';
import { basename } from 'path';
import {
commandInAction,
COMMANDS,
program,
tempDirURL
} from '../cli.js';
import {
listChoices,
copyFolderSync,
getArchiveNameFromURL,
getOSString,
isWindows,
isMacOS,
isLinux
} from '../utils.js';
import {
deleteDownloadedArchive,
downloadArchive,
getServerSideToolFolder,
installAccelerator,
isFA,
isFDS,
isForOS,
isClientSideTool,
installClientSideTool,
isServerSideTool,
getTool,
isValidTool,
getServerToolByOS,
handleServiceUpdateProcess,
updateAndRunGUITool,
updateAndRunServiceTool,
uninstallServiceTool,
checkIfServiceInstalled,
stopService,
closeGUITool
} from './base.js';
import { promisify } from 'util';
// import { exec as execNonPromise } from 'child_process';
// const exec = promisify(execNonPromise);
const TOOLS_TO_UPDATE = [{
name: 'fds',
value: 'fds',
title: 'Flexmonster Data Server',
description: 'Flexmonster Data Server (a server-side tool)',
guiToolName: 'Flexmonster Admin Panel',
fileToBestGuess: 'flexmonster-config.json',
os: [{
name: 'Linux (x64)',
url: 'https://dist.flexmonster.com/flexmonster-data-server/2.9/latest/FlexmonsterDataServer-linux-x64.tar.gz',
executable: 'flexmonster-data-server',
serviceExecutable: 'service-install',
serviceUninstaller: 'service-uninstall',
serviceStatusCommand: 'systemctl status flexmonster-data-server.service',
serviceStopCommand: 'sudo systemctl stop flexmonster-data-server.service',
guiToolStopCommand: 'killall flexmonster-admin-panel',
guiExecutable: 'Flexmonster-Admin-Panel.AppImage'
},
{
name: 'Linux (ARM64)',
url: 'https://dist.flexmonster.com/flexmonster-data-server/2.9/latest/FlexmonsterDataServer-linux-arm64.tar.gz',
executable: 'flexmonster-data-server',
serviceExecutable: 'service-install',
serviceUninstaller: 'service-uninstall',
serviceStatusCommand: 'systemctl status flexmonster-data-server.service',
serviceStopCommand: 'sudo systemctl stop flexmonster-data-server.service',
guiToolStopCommand: 'killall flexmonster-admin-panel',
guiExecutable: 'Flexmonster-Admin-Panel.AppImage'
},
{
name: 'macOS (x64)',
url: 'https://dist.flexmonster.com/flexmonster-data-server/2.9/latest/FlexmonsterDataServer-osx-x64.tar.gz',
executable: 'flexmonster-data-server',
serviceExecutable: 'service-install.sh',
serviceUninstaller: 'service-uninstall.sh',
serviceStatusCommand: 'ls ~/Library/LaunchAgents/com.flexmonster.DataServer.plist',
serviceStopCommand: 'launchctl unload -w ~/Library/LaunchAgents/com.flexmonster.DataServer.plist',
guiToolStopCommand: 'killall "Flexmonster Admin Panel"',
guiExecutable: 'Flexmonster-Admin-Panel.dmg'
},
{
name: 'macOS (ARM64)',
url: 'https://dist.flexmonster.com/flexmonster-data-server/2.9/latest/FlexmonsterDataServer-osx-arm64.tar.gz',
executable: 'flexmonster-data-server',
serviceExecutable: 'service-install.sh',
serviceUninstaller: 'service-uninstall.sh',
serviceStatusCommand: 'ls ~/Library/LaunchAgents/com.flexmonster.DataServer.plist',
serviceStopCommand: 'launchctl unload -w ~/Library/LaunchAgents/com.flexmonster.DataServer.plist',
guiToolStopCommand: 'killall "Flexmonster Admin Panel"',
guiExecutable: 'Flexmonster-Admin-Panel.dmg'
},
{
name: 'Windows (x64)',
url: 'https://dist.flexmonster.com/flexmonster-data-server/2.9/latest/FlexmonsterDataServer-win-x64.zip',
executable: 'flexmonster-data-server',
serviceExecutable: 'service-install.bat',
serviceUninstaller: 'service-uninstall.bat',
serviceStatusCommand: 'sc query FlexmonsterDataServer',
serviceStopCommand: 'sc stop FlexmonsterDataServer',
guiToolStopCommand: 'taskkill /F /IM "Flexmonster Admin Panel.exe"',
guiExecutable: 'Flexmonster-Admin-Panel.exe'
},
{
name: 'Windows (x86)',
url: 'https://dist.flexmonster.com/flexmonster-data-server/2.9/latest/FlexmonsterDataServer-win-x86.zip',
executable: 'flexmonster-data-server',
serviceExecutable: 'service-install.bat',
serviceUninstaller: 'service-uninstall.bat',
serviceStatusCommand: 'sc query FlexmonsterDataServer',
serviceStopCommand: 'sc stop FlexmonsterDataServer',
guiToolStopCommand: 'taskkill /F /IM "Flexmonster Admin Panel.exe"',
guiExecutable: 'Flexmonster-Admin-Panel.exe',
}
],
},
{
name: 'accelerator',
value: 'accelerator',
title: 'Flexmonster Accelerator for SSAS',
description: 'Flexmonster Accelerator for SSAS (a server-side tool)',
os: [{
name: 'Windows (x64)',
url: 'https://dist.flexmonster.com/flexmonster-accelerator/2.9/latest/FlexmonsterAccelerator.zip',
dir: '',
executable: 'Flexmonster Accelerator.msi',
},
{
name: 'Windows (x86)',
url: 'https://dist.flexmonster.com/flexmonster-accelerator/2.9/latest/FlexmonsterAccelerator.zip',
dir: '',
executable: 'Flexmonster Accelerator.msi',
}
],
},
{
name: 'ng-flexmonster',
value: 'ng-flexmonster',
title: 'Flexmonster Pivot wrapper for Angular 15 and older projects (legacy)',
description: 'Flexmonster Pivot wrapper for Angular 15 and older projects (a legacy node module)',
fileToBestGuess: 'package.json',
module: 'ng-flexmonster',
},
{
name: 'ngx-flexmonster',
value: 'ngx-flexmonster',
title: 'Flexmonster Pivot wrapper for Angular 14+ projects',
description: 'Flexmonster Pivot wrapper for Angular 14+ projects (a node module)',
fileToBestGuess: 'package.json',
module: 'ngx-flexmonster',
},
{
name: 'react-flexmonster',
value: 'react-flexmonster',
title: 'Flexmonster Pivot wrapper for React projects',
description: 'Flexmonster Pivot wrapper for React projects (a node module)',
fileToBestGuess: 'package.json',
module: 'react-flexmonster',
},
{
name: 'vue-flexmonster',
value: 'vue-flexmonster',
title: 'Flexmonster Pivot for Vue projects',
description: 'Flexmonster Pivot for Vue projects (a node module)',
fileToBestGuess: 'package.json',
module: 'vue-flexmonster',
},
{
name: 'js-flexmonster',
value: 'flexmonster',
title: 'Flexmonster Pivot',
description: 'Flexmonster Pivot (a node module)',
fileToBestGuess: 'package.json',
module: 'flexmonster',
},
];
function toolBestGuess() {
for (let i = 0; i < TOOLS_TO_UPDATE.length; i++) {
const toolObj = TOOLS_TO_UPDATE[i];
const fileToBestGuess = toolObj.fileToBestGuess;
// console.log(">>>>", fileToBestGuess, fs.existsSync(fileToBestGuess));
// console.log(">>>>", "flexmonster-data-server", fs.existsSync("flexmonster-data-server"));
if (fs.existsSync(fileToBestGuess)) {
if (toolObj.module !== undefined) {
var content = fs.readFileSync(fileToBestGuess, 'utf8');
const dIdx = content.indexOf("dependencies");
if (dIdx > -1 && content.indexOf('"' + toolObj.module + '":') > dIdx) {
//console.log(">>!!!", toolObj.name, toolObj.value);
return toolObj.value;
}
} else {
return toolObj.value;
}
}
}
return undefined;
}
const Q_COMMAND_UPDATE = [{
type: 'list',
name: 'tool',
message: 'Choose what should be updated:',
choices: listChoices(TOOLS_TO_UPDATE),
when: function (answers) {
const tool = toolBestGuess();
if (tool !== undefined) {
answers.tool = tool;
return false;
}
return true;
}
}];
function composeCommandUpdateQuestions(argAnswers) {
return Q_COMMAND_UPDATE;
}
export function initUpdateCommand() {
program.command('update [tool|module]') // sub-command name
.alias('u') // alternative sub-command
.description(COMMANDS[2].description) // command description
.action(function (tool, args) { // function to execute when command is used
commandInAction();
let argAnswers = {};
const toolGuess = toolBestGuess();
if (isValidTool(TOOLS_TO_UPDATE, tool)) {
executeUpdate(tool, args);
} else if (isValidTool(TOOLS_TO_UPDATE, toolGuess)) {
tool = toolGuess;
executeUpdate(tool, args);
} else {
if (tool !== undefined) {
console.log('');
console.log('~ update command has an invalid tool name: %s', tool);
console.log('');
}
helpUpdate(argAnswers);
}
})
.addHelpText('after', `
Examples:
flexmonster update accelerator
flexmonster update fds
flexmonster update flexmonster
flexmonster update js-flexmonster
flexmonster update ng-flexmonster
flexmonster update react-flexmonster
flexmonster update vue-flexmonster
`);
}
export function helpUpdate(argAnswers) {
escExit();
inquirer.prompt(composeCommandUpdateQuestions(argAnswers))
.then(function (answers) {
executeUpdate(answers["tool"], answers);
});
}
async function executeUpdate(tool, args) {
tool = tool.toLowerCase();
const toolObj = getTool(TOOLS_TO_UPDATE, tool);
let wasServiceInstalled;
const osObj = getServerToolByOS(TOOLS_TO_UPDATE, tool, getOSString());
if (!isForOS(TOOLS_TO_UPDATE, tool)) {
console.log('');
console.log('~ there is no %s for %s operating system', toolObj.title, chalk.blueBright(getOSString()));
// if (isMacOS(process.platform)) console.log(' try %s as a possible solution or', chalk.underline('https://www.flexmonster.com/doc/troubleshooting-cli/#no-fds-for-macos'));
console.log(' contact us at %s to address any questions', chalk.underline('https://www.flexmonster.com/technical-support/'));
console.log('');
} else {
console.log('');
if (isFDS(tool)) {
console.log(chalk.italic(`Preparing to update ${toolObj.title}`));
// Check if flexmonster-data-server directory exists in cwd
if (fs.existsSync(osObj.executable) && fs.lstatSync(osObj.executable).isDirectory()) {
try {
// Try switching to flexmonster-data-server directory
process.chdir(osObj.executable);
} catch (err) { }
}
wasServiceInstalled = await checkIfServiceInstalled(osObj);
if (wasServiceInstalled === true) {
// console.log("need to stop service");
const stoppedService = await stopService(toolObj, osObj);
const closedAdminPanel = await closeGUITool(osObj);
// console.log("service and admin panel were closed ", stoppedService, closedAdminPanel);
if (stoppedService) console.log(chalk.green(`${toolObj.title} service is stopped`));
if (closedAdminPanel) console.log(chalk.green(`${toolObj.guiToolName} is stopped`));
}
// else {
// console.log("service was not previously installed");
// }
}
console.log('');
console.log('--- UPDATE ---');
console.log('');
const tasks = new Listr([
{
title: 'Download ' + toolObj.title,
task: (ctx, task) => downloadServerSideTool(osObj, task)
.catch(() => {
const url = osObj.url;
throw new Error(chalk.redBright.bold("Error:") + " download has failed. \n" + chalk.yellowBright.bold("Recommendation:") + " Try to download it from here: " + chalk.yellowBright.bold.underline(url));
}),
enabled: () => isServerSideTool(tool),
},
{
title: 'Unpack files to ' + (isFDS(tool) ? 'this folder' : chalk.green.bold(getServerSideToolFolder(tool))),
task: () => unpackServerSideTool(tool, osObj)
.then(() => {
const archiveName = getArchiveNameFromURL(osObj.url);
deleteDownloadedArchive(archiveName);
})
.catch((error) => {
const archiveName = getArchiveNameFromURL(osObj.url);
throw new Error(error + chalk.redBright.bold("Error:") + " unpacking has failed. \n" + chalk.yellowBright.bold("Recommendation:") + " Try to unpack it manually: " + chalk.yellowBright.bold(archiveName));
}),
enabled: () => isServerSideTool(tool),
},
{
title: 'Update ' + toolObj.title,
task: () => installServerSideTool(tool, osObj),
enabled: () => isFA(tool),
},
{
title: 'Update ' + toolObj.title,
task: () => installClientSideTool(toolObj),
enabled: () => isClientSideTool(tool),
},
{
title: `Uninstall previous ${toolObj.title} service version`,
task: () => uninstallServiceTool(osObj, toolObj)
.catch(error => {
throw error;
}),
enabled: () => !isLinux(process.platform) && wasServiceInstalled && isFDS(tool)
},
{
title: `Update ${toolObj.title} service`,
task: () => updateAndRunServiceTool(osObj, toolObj)
.catch(error => {
throw error;
}),
enabled: () => !isLinux(process.platform) && wasServiceInstalled && isFDS(tool)
},
{
title: `Update ${toolObj.guiToolName} (the service GUI tool)`
+ (isMacOS(process.platform) ? `\n${chalk.blueBright(`Hint: please use Finder to drag the app to the "Applications" folder`)}` : ""),
task: () => updateAndRunGUITool(osObj, toolObj)
.catch(error => {
throw error;
}),
enabled: () => !isLinux(process.platform) && wasServiceInstalled && isFDS(tool)
}
]);
tasks.run()
.then(() => {
console.log(chalk.green.bold('DONE'));
console.log('');
//reinstall service and start Admin Panel
if (wasServiceInstalled && isServerSideTool(tool) && !isFA(tool) && !(isWindows(process.platform) || isMacOS(process.platform))) {
console.log(chalk.italic(`Updating ${toolObj.title} service (requires sudo privileges)`));
console.log('');
uninstallServiceTool(osObj, toolObj)
.then(() => {
console.log(chalk.green(`Uninstalled previous ${toolObj.title} service version`))
console.log('');
handleServiceUpdateProcess(tool, osObj, toolObj)
})
.catch((error) => {
console.log(chalk.red("Error: Failed to uninstall previous version"))
console.log(chalk.red(`Details: ${error.message}`))
console.log('');
handleServiceUpdateProcess(tool, osObj, toolObj)
});
}
})
.catch(error => {
console.error(error.message);
console.log('');
});
}
}
async function downloadServerSideTool(osObj, task) {
const url = osObj.url;
const archiveName = getArchiveNameFromURL(osObj.url);
return await downloadArchive(url, archiveName, task);
}
function unpackServerSideTool(tool, osObj) {
//create temp directory for unpacking
if (!fs.existsSync(tempDirURL)) {
fs.mkdirSync(tempDirURL);
}
try {
const archiveName = getArchiveNameFromURL(osObj.url);
const toolObj = getTool(TOOLS_TO_UPDATE, tool);
let file = decompress(archiveName, tempDirURL, {
filter: file => basename(file.path) !== toolObj.fileToBestGuess
});
return new Promise((resolve, reject) => {
file.then((files) => {
let projectFolderURL;
if (isFDS(tool)) {
projectFolderURL = ".";
try {
if (osObj.dir !== undefined) {
copyFolderSync(tempDirURL + osObj.dir, projectFolderURL);
} else {
copyFolderSync(tempDirURL, projectFolderURL);
}
sync(tempDirURL);
resolve();
} catch (error) {
sync(tempDirURL);
reject(error);
}
} else if (isFA(tool)) {
projectFolderURL = "." + getServerSideToolFolder(tool);
if (fs.existsSync(projectFolderURL)) {
sync(projectFolderURL);
}
fs.mkdirSync(projectFolderURL);
if (osObj.dir !== undefined) {
copyFolderSync(tempDirURL + osObj.dir, projectFolderURL);
} else {
copyFolderSync(tempDirURL, projectFolderURL);
}
sync(tempDirURL);
resolve();
}
});
file.catch((error) => {
sync(tempDirURL);
reject(error);
});
});
} catch (error) {
sync(tempDirURL);
return Promise.reject(new Error('Failed to unpack'));
}
}
// for the Accelerator only
function installServerSideTool(tool, osObj) {
const url = "." + getServerSideToolFolder(tool);
return installAccelerator(url, osObj.executable);
}