flexmonster-cli
Version:
CLI for Flexmonster Pivot Table & Charts installation
728 lines (681 loc) • 30.8 kB
JavaScript
import {
program,
COMMANDS,
commandInAction
} from '../cli.js';
import {
isMacOS,
isWindows,
listChoices
} from '../utils.js';
import {
deleteDownloadedArchive,
getProjectURL,
getProjectArchiveName,
downloadProject,
unpackProject,
getProjectFolder,
getProjectString,
NO_FRAMEWORK
} from './base.js';
import inquirer from 'inquirer';
import escExit from 'esc-exit';
import Listr from 'listr';
import {
projectInstall
} from 'pkg-install';
import chalk from 'chalk';
import { spawn } from 'child_process';
import open from 'open';
import path from 'path';
const PROJECTS = [{
name: 'angular',
value: 'angular',
title: 'Angular',
url: 'https://github.com/flexmonster/pivot-angular/archive/master.zip',
archiveName: 'pivot-angular-master.zip',
dir: '/pivot-angular-master',
hasDependencies: true,
runnable: true,
},
{
name: 'nextjs',
value: 'nextjs',
title: 'NextJS',
url: 'https://github.com/flexmonster/pivot-react/archive/master.zip',
archiveName: 'pivot-react-master.zip',
dir: '/pivot-react-master/nextjs-ts',
hasDependencies: true,
runnable: true
},
{
name: 'react',
value: 'react',
title: 'React',
url: 'https://github.com/flexmonster/pivot-react/archive/master.zip',
archiveName: 'pivot-react-master.zip',
configuration: [
{
name: 'nextjs',
value: 'nextjs',
title: 'NextJS',
description: 'recommended',
dir: '/pivot-react-master/nextjs-ts',
hasDependencies: true,
runnable: true
},
{
name: 'es6',
value: 'es6',
title: 'ES6',
dir: '/pivot-react-master/ES6',
hasDependencies: true,
runnable: true,
},
{
name: 'typescript',
value: 'typescript',
title: 'TypeScript',
dir: '/pivot-react-master/typescript',
hasDependencies: true,
runnable: true,
}
]
},
{
name: 'vue',
value: 'vue',
title: 'Vue',
url: 'https://github.com/flexmonster/pivot-vue/archive/master.zip',
archiveName: 'pivot-vue-master.zip',
version: [{
name: 'vue 2',
value: '2',
title: 'Vue 2',
dir: '/pivot-vue-master/vue2',
configuration: [{
name: 'es6',
value: 'es6',
title: 'ES6',
dir: '/pivot-vue-master/vue2/ES6',
hasDependencies: true,
runnable: true,
},
{
name: 'typescript',
value: 'typescript',
title: 'TypeScript',
dir: '/pivot-vue-master/vue2/typescript',
hasDependencies: true,
runnable: true,
}
]
}, {
name: 'vue 3',
value: '3',
title: 'Vue 3',
dir: '/pivot-vue-master/vue3',
configuration: [{
name: 'es6',
value: 'es6',
title: 'ES6',
dir: '/pivot-vue-master/vue3/ES6',
hasDependencies: true,
runnable: true,
},
{
name: 'typescript',
value: 'typescript',
title: 'TypeScript',
dir: '/pivot-vue-master/vue3/typescript',
hasDependencies: true,
runnable: true,
}
]
}]
},
{
name: 'electron',
value: 'electron',
title: 'Electron',
url: 'https://github.com/flexmonster/pivot-electron/archive/master.zip',
archiveName: 'pivot-electron-master.zip',
dir: '/pivot-electron-master',
hasDependencies: true,
runnable: true,
},
{
name: NO_FRAMEWORK,
value: NO_FRAMEWORK,
title: '-',
configuration: [{
name: 'javascript',
value: 'javascript',
title: 'JavaScript',
url: 'https://dist.flexmonster.com/flexmonster-pivot-javascript/2.9/latest/FlexmonsterPivotTable.zip',
archiveName: 'pivot-javascript-master.zip',
hasDependencies: false,
runnable: false,
browserOpennable: true
},
{
name: 'typescript',
value: 'typescript',
title: 'TypeScript',
url: 'https://github.com/flexmonster/pivot-typescript/archive/master.zip',
archiveName: 'pivot-typescript-master.zip',
dir: '/pivot-typescript-master/simpleProject',
hasDependencies: true,
runnable: true,
webpackDir: '/pivot-typescript-master/webpackProject',
webpackRunnable: true,
}
]
}
];
function getProject(framework) {
for (let i = 0; i < PROJECTS.length; i++) {
if (PROJECTS[i].value == framework) {
return PROJECTS[i];
}
}
return undefined;
}
function hasProject(framework) {
return getProject(framework) != undefined;
}
function getFrameworkConfigurations(framework, version) {
const projectObj = getProject(framework);
if (hasFrameworkVersions(framework)) {
const versionObj = getFrameworkVersion(framework, version);
return versionObj && versionObj.configuration != undefined ? versionObj.configuration : undefined;
}
return projectObj.configuration;
}
function getFrameworkVersions(framework) {
const projectObj = getProject(framework);
return projectObj.version;
}
function hasFrameworkConfigurations(framework, version) {
const configurations = getFrameworkConfigurations(framework, version);
return configurations != undefined && configurations.length > 0;
}
function hasFrameworkVersions(framework) {
const versions = getFrameworkVersions(framework);
return versions != undefined && versions.length > 0;
}
function getFrameworkConfiguration(framework, version, configuration) {
const configurations = getFrameworkConfigurations(framework, version);
if (configurations != undefined) {
for (let i = 0; i < configurations.length; i++) {
if (configurations[i].value == configuration) {
return configurations[i];
}
}
}
return undefined;
}
function getFrameworkVersion(framework, version) {
const versions = getFrameworkVersions(framework);
if (versions != undefined) {
for (let i = 0; i < versions.length; i++) {
if (versions[i].value == version) {
return versions[i];
}
}
}
return undefined;
}
function hasFrameworkConfiguration(framework, version, configuration) {
return getFrameworkConfiguration(framework, version, configuration) != undefined;
}
function hasFrameworkVersion(framework, version) {
return getFrameworkVersion(framework, version) != undefined;
}
function hasWebpack(framework, version, configuration) {
const configurationObj = getFrameworkConfiguration(framework, version, configuration);
if (configurationObj != undefined) {
return configurationObj.webpackDir != undefined;
}
return false;
}
function isValidFramework(framework) {
if (framework == undefined) return false;
framework = framework.toLowerCase();
return hasProject(framework);
}
function isValidConfiguration(framework, version, configuration, strictMode = true) {
if (!isValidFramework(framework)) return false;
framework = framework.toLowerCase();
if (!strictMode && !hasFrameworkConfigurations(framework, version)) return true;
if (configuration == undefined) return false;
configuration = configuration.toLowerCase();
return hasFrameworkConfiguration(framework, version, configuration);
}
function isValidFrameworkVersion(framework, version, strictMode = true) {
if (!isValidFramework(framework)) return false;
framework = framework.toLowerCase();
if (!strictMode && !hasFrameworkVersions(framework)) return true;
if (version == undefined) return false;
version = version.toLowerCase();
return hasFrameworkVersion(framework, version);
}
function isValidNoFrameworkConfiguration(configuration, version) {
return isValidConfiguration(NO_FRAMEWORK, version, configuration);
}
function isValidWebpack(framework, version, configuration, webpack, strictMode = true) {
if (!isValidFramework(framework)) return false;
framework = framework.toLowerCase();
if (!strictMode && !hasFrameworkConfigurations(framework)) return true;
configuration = (configuration != undefined) ? configuration.toLowerCase() : undefined;
if (!hasWebpack(framework, version, configuration)) return false;
const configurationObj = getFrameworkConfiguration(framework, version, configuration);
const webpackValue = webpack == true || (webpack != false && webpack != undefined && webpack.toLowerCase() == 'webpack');
return (webpackValue && configurationObj != undefined && configurationObj.webpackDir != undefined) ||
(!strictMode && (configurationObj == undefined || configurationObj.webpackDir == undefined));
}
function isValidToInstall(framework, version, configuration) {
if (!isValidFramework(framework)) return false;
framework = framework.toLowerCase();
const projectObj = getProject(framework);
version = (version != undefined) ? version.toLowerCase() : undefined;
const versionObj = getFrameworkVersion(framework, version);
if (versionObj != undefined && !isValidFrameworkVersion(framework, version)) return false;
configuration = (configuration != undefined) ? configuration.toLowerCase() : undefined;
const configurationObj = getFrameworkConfiguration(framework, version, configuration);
return (configurationObj != undefined && configurationObj.hasDependencies == true) || projectObj.hasDependencies == true || (versionObj != undefined && (versionObj.runnable == true));
}
function isValidToRun(framework, version, configuration, webpack) {
if (!isValidFramework(framework)) return false;
framework = framework.toLowerCase();
const projectObj = getProject(framework);
version = (version != undefined) ? version.toLowerCase() : undefined;
const versionObj = getFrameworkVersion(framework, version);
if (versionObj != undefined && !isValidFrameworkVersion(framework, version)) return false;
configuration = (configuration != undefined) ? configuration.toLowerCase() : undefined;
const configurationObj = getFrameworkConfiguration(framework, version, configuration);
return (configurationObj != undefined && (configurationObj.runnable == true || (webpack == true && configurationObj.webpackRunnable == true))) || projectObj.runnable == true || (versionObj != undefined && (versionObj.runnable == true));
}
function isValidToOpen(framework, version, configuration, webpack) {
if (!isValidFramework(framework)) return false;
framework = framework.toLowerCase();
const projectObj = getProject(framework);
version = (version != undefined) ? version.toLowerCase() : undefined;
const versionObj = getFrameworkVersion(framework, version);
if (versionObj != undefined && !isValidFrameworkVersion(framework, version)) return false;
configuration = (configuration != undefined) ? configuration.toLowerCase() : undefined;
const configurationObj = getFrameworkConfiguration(framework, version, configuration);
return (configurationObj != undefined && (configurationObj.browserOpennable == true)) || projectObj.browserOpennable == true || (versionObj != undefined && (versionObj.browserOpennable == true));
}
const Q_COMMAND_CREATE = [{
type: 'list',
name: 'framework',
message: 'Choose a framework to create a sample Flexmonster project for:',
choices: listChoices(PROJECTS)
},
{
type: 'list',
name: 'version',
message: 'Choose a framework version for a sample Flexmonster project:',
choices: function (answers) {
return listChoices(getFrameworkVersions(answers.framework));
},
when: function (answers) {
return hasFrameworkVersions(answers.framework);
}
},
{
type: 'list',
name: 'configuration',
message: 'Choose a configuration for a sample Flexmonster project:',
choices: function (answers) {
return listChoices(getFrameworkConfigurations(answers.framework, answers.version));
},
when: function (answers) {
return hasFrameworkConfigurations(answers.framework, answers.version);
}
},
{
type: 'confirm',
name: 'webpack',
message: 'Should webpack be used in the project?',
default: false,
when: function (answers) {
return hasWebpack(answers.framework, answers.version, answers.configuration);
}
},
{
type: 'confirm',
name: 'run',
message: 'Should the project be run automatically?',
default: false,
when: function (answers) {
return isValidToRun(answers.framework, answers.version, answers.configuration, answers.webpack);
}
},
{
type: 'confirm',
name: 'run',
message: 'Should the project be opened in the default browser automatically?',
default: false,
when: function (answers) {
return isValidToOpen(answers.framework, answers.version, answers.configuration, answers.webpack);
}
},
{
type: 'confirm',
name: 'install',
message: 'Should all dependencies be installed automatically for the project? (It may take a while)',
default: false,
when: function (answers) {
return !answers.run && isValidToInstall(answers.framework, answers.version, answers.configuration);
}
}
];
function composeCommandCreateQuestions(argAnswers) {
if (argAnswers != undefined) {
if (argAnswers.framework != undefined) {
console.log('Framework: %s', argAnswers.framework);
Q_COMMAND_CREATE[0].when = function (answers) {
answers.framework = argAnswers.framework; //set what we know already
return false; //do not show this question
};
}
if (argAnswers.version != undefined) {
console.log('Version: %s', argAnswers.version);
Q_COMMAND_CREATE[1].when = function (answers) {
answers.version = argAnswers.version; //set what we know already
return false; //do not show this question
};
}
if (argAnswers.configuration != undefined) {
console.log('Configuration: %s', argAnswers.configuration);
Q_COMMAND_CREATE[2].when = function (answers) {
answers.configuration = argAnswers.configuration; //set what we know already
return false; //do not show this question
};
}
if (argAnswers.webpack != undefined) {
console.log('Webpack: %s', argAnswers.webpack);
Q_COMMAND_CREATE[3].when = function (answers) {
answers.webpack = argAnswers.webpack; //set what we know already
return false; //do not show this question
};
}
if (argAnswers.run) {
Q_COMMAND_CREATE[4].when = function (answers) {
answers.run = argAnswers.run && (isValidToRun(answers.framework, answers.version, answers.configuration, answers.webpack) || isValidToOpen(answers.framework, answers.version, answers.configuration, answers.webpack));
return false; //do not show this question
};
}
if (argAnswers.run) {
Q_COMMAND_CREATE[5].when = function (answers) {
answers.run = argAnswers.run && isValidToOpen(answers.framework, answers.version, answers.configuration, answers.webpack);
return false; //do not show this question
};
}
if (argAnswers.install) {
Q_COMMAND_CREATE[6].when = function (answers) {
answers.install = argAnswers.install && isValidToInstall(answers.framework, answers.version, answers.configuration);
return false; //do not show this question
};
}
}
return Q_COMMAND_CREATE;
}
export function initCreateCommand() {
program.command('create [framework] [version] [configuration] [webpack]') // sub-command name
.alias('c') // alternative sub-command
.description(COMMANDS[0].description) // command description
.option('-i, --install', "true, install automatically all the npm dependencies for the project", false) // false by default
.option('-r, --run', "true, run automatically the project after the creation", false) // false by default
.action(function (framework, version, configuration, webpack, args) { // function to execute when command is used
commandInAction();
let argAnswers = {};
argAnswers.install = args.install || args.run;
argAnswers.run = args.run;
if (isValidFramework(framework)) {
framework = framework.toLowerCase();
argAnswers.framework = framework;
if (isValidFrameworkVersion(framework, version, false)) {
version = (version === undefined) ? version : version.toLowerCase();
if (hasFrameworkVersion(framework, version)) {
argAnswers.version = version;
} else {
configuration = version;
version = undefined;
}
}
if (isValidConfiguration(framework, version, configuration, false)) {
configuration = (configuration === undefined) ? configuration : configuration.toLowerCase();
if (hasFrameworkConfiguration(framework, version, configuration)) {
argAnswers.configuration = configuration;
helpCreate(argAnswers);
} else {
if (configuration !== undefined) {
console.log('');
console.log('~ %s is not applicable to framework name: %s', configuration, framework);
console.log('');
}
helpCreate(argAnswers);
}
} else {
helpCreate(argAnswers);
}
} else if (isValidNoFrameworkConfiguration(framework, version)) {
webpack = version;
configuration = framework;
framework = NO_FRAMEWORK;
version = undefined;
configuration = (configuration === undefined) ? configuration : configuration.toLowerCase();
argAnswers.framework = framework;
// argAnswers.version = version;
argAnswers.configuration = configuration;
if (isValidWebpack(framework, version, configuration, webpack, false)) {
argAnswers.webpack = true;
helpCreate(argAnswers);
} else {
helpCreate(argAnswers);
}
} else {
if (framework !== undefined) {
console.log('');
console.log('~ create command has an invalid framework name: %s', framework);
console.log('');
}
helpCreate(argAnswers);
}
})
.addHelpText('after', `
Examples:
flexmonster create angular
flexmonster create angular -i
flexmonster create angular -r
flexmonster create nextjs
flexmonster create nextjs -i
flexmonster create nextjs -r
flexmonster create react nextjs
flexmonster create react nextjs -i
flexmonster create react nextjs -r
flexmonster create react es6
flexmonster create react es6 -i
flexmonster create react es6 -r
flexmonster create react typescript
flexmonster create react typescript -i
flexmonster create react typescript -r
flexmonster create vue 2 typescript
flexmonster create vue 2 typescript -i
flexmonster create vue 2 typescript -r
flexmonster create vue 2 es6
flexmonster create vue 2 es6 -i
flexmonster create vue 2 es6 -r
flexmonster create vue 3 typescript
flexmonster create vue 3 typescript -i
flexmonster create vue 3 typescript -r
flexmonster create vue 3 es6
flexmonster create vue 3 es6 -i
flexmonster create vue 3 es6 -r
flexmonster create typescript
flexmonster create typescript -i
flexmonster create typescript -r
flexmonster create typescript webpack
flexmonster create typescript webpack -i
flexmonster create typescript webpack -r
flexmonster create javascript
flexmonster create javascript -r
flexmonster create electron
flexmonster create electron -i
flexmonster create electron -r
`);
}
export function helpCreate(argAnswers) {
escExit();
inquirer.prompt(composeCommandCreateQuestions(argAnswers))
.then(function (answers) {
executeCreate(answers["framework"], answers["version"], answers["configuration"], answers["webpack"], answers);
});
}
async function executeCreate(framework, version, configuration, webpack, args) {
framework = framework.toLowerCase();
version = (version != undefined) ? version.toLowerCase() : undefined;
configuration = (configuration != undefined) ? configuration.toLowerCase() : undefined;
webpack = webpack == true || (webpack != false && webpack != undefined && webpack.toLowerCase() == 'webpack');
const projectObj = getProject(framework);
const versionObj = getFrameworkVersion(framework, version);
const configurationObj = getFrameworkConfiguration(framework, version, configuration);
console.log('');
console.log('--- CREATE ---');
if (webpack) {
configurationObj.dir = configurationObj.webpackDir;
}
if (projectObj != undefined && projectObj.url != "") {
const tasks = new Listr([{
title: 'Download project ' + getProjectString(projectObj, versionObj, configurationObj),
task: (ctx, task) => downloadProject(projectObj, versionObj, configurationObj, task)
.catch(() => {
const url = getProjectURL(projectObj, versionObj, configurationObj);
throw new Error(chalk.redBright.bold("Error:") + " download has failed. \n" + chalk.yellowBright.bold("Recommendation:") + " Try to download a sample project from here: " + chalk.yellowBright.bold.underline(url));
}),
},
{
title: 'Unpack files to ' + chalk.green.bold(getProjectFolder(projectObj, versionObj, configurationObj, webpack)),
task: () => unpackProject(projectObj, versionObj, configurationObj, webpack)
.then(() => {
const archiveName = getProjectArchiveName(projectObj, versionObj, configurationObj);
deleteDownloadedArchive(archiveName);
})
.catch((error) => {
const archiveName = getProjectArchiveName(projectObj, versionObj, configurationObj);
throw new Error(chalk.redBright.bold("Error:") + " unpacking has failed. \n" + chalk.yellowBright.bold("Recommendation:") + " Try to unpack it manually: " + chalk.yellowBright.bold(archiveName));
}),
},
{
title: 'Install dependencies (It may take a while)',
task: async () => {
const url = "." + getProjectFolder(projectObj, versionObj, configurationObj, webpack);
await projectInstall({
cwd: url
});
return;
},
enabled: () => (args.install && isValidToInstall(framework, version, configuration)) || (args.run && isValidToRun(framework, version, configuration, webpack)),
},
{
title: (isWindows(process.platform) || isMacOS(process.platform)) ? 'Run project' : 'Prepare to run project',
task: () => runProject(projectObj, versionObj, configurationObj, webpack)
.catch(error => {
throw error;
}),
enabled: () => args.run && isValidToRun(framework, version, configuration, webpack),
},
{
title: 'Open in browser',
task: async () => {
const url = process.cwd() + "/" + getProjectFolder(projectObj, versionObj, configurationObj, webpack);
// Opens in the default browser
await open(url + '/index.html');
return;
},
enabled: () => args.run && isValidToOpen(framework, version, configuration, webpack),
}
]);
tasks.run()
.then(() => {
console.log(chalk.green.bold('READY'));
console.log('');
if (args.run && isValidToRun(framework, version, configuration, webpack) && isWindows(process.platform)) {
console.log('Press Ctrl+C here to enter new command (PS: This will not stop the started project)');
} else if (args.run && isValidToRun(framework, version, configuration, webpack) && !isMacOS(process.platform)) {
console.log('Starting to run project:');
} else {
console.log('Check the %s folder', chalk.green.bold(getProjectFolder(projectObj, versionObj, configurationObj, webpack)));
}
console.log('');
})
.catch(error => {
console.log('');
if (isMacOS(process.platform) && error.message && error.message.indexOf('71:172:') > -1 || error.message.indexOf('Not authorized to send Apple events to Terminal') > -1) {
console.error(`Recommendation: Please allow ${chalk.bold('Terminal')} to control other apps in ${chalk.bold('System Preferences > Security & Privacy > Automation')} to be able to run the project using Flexmonster CLI.`);
} else {
console.error(error.message);
}
console.log('');
});
}
}
function runProject(projectObj, versionObj, configurationObj, webpack) {
const projectFolder = getProjectFolder(projectObj, versionObj, configurationObj, webpack);
var url = "." + projectFolder;
return new Promise((resolve, reject) => {
try {
var childProcess;
if (isWindows(process.platform)) {
childProcess = spawn('cmd', ['/c', 'npm start'], {
cwd: url,
detached: true,
shell: true
});
} else if (isMacOS(process.platform)) {
url = path.resolve('') + projectFolder;
childProcess = spawn('osascript', [
'-e', 'tell application "Terminal" to activate', // ira: activates "Terminal" even if it was closed before
'-e', 'tell application "Terminal" to do script "cd ' + url + '"', // ira: cd to the URL in new "Terminal" window
'-e', 'tell application "Terminal" to activate', // ira: activates this new window
'-e', 'tell application "Terminal" to do script "npm start" in selected tab of the front window' // ira: runs start script
]);
} else {
childProcess = spawn('npm start', {
cwd: url,
shell: true,
stdio: 'inherit'
});
resolve();
}
if (isWindows(process.platform) || isMacOS(process.platform)) {
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)) {
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());
});
}
}
} catch (error) {
return reject(new Error('Failed to run'));
}
});
}