UNPKG

flexmonster-cli

Version:

CLI for Flexmonster Pivot Table & Charts installation

728 lines (681 loc) 30.8 kB
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')); } }); }