zombiebox
Version:
ZombieBox is a JavaScript framework for development of Smart TV and STB applications
357 lines (313 loc) • 8.15 kB
JavaScript
/*
* This file is part of the ZombieBox package.
*
* Copyright © 2012-2019, Interfaced
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
const path = require('path');
const fs = require('fs');
const chalk = require('chalk');
const yargs = require('yargs');
const {findPackageJson} = require('../lib/utils');
const Application = require('../lib/application');
const TemplateHelper = require('../lib/template-helper');
const Scaffolding = require('../lib/scaffolding');
/**
*/
class CLI {
/**
*/
constructor() {
/**
* @type {?Application}
* @protected
*/
this._application = null;
/**
* @type {TemplateHelper}
* @protected
*/
this._templateHelper = new TemplateHelper(
() => {
let locations = [path.resolve(__dirname, '../templates')];
if (this._application) {
locations = locations.concat(this._application.getPathHelper().getTemplateLocations());
}
return locations;
}
);
/**
* @type {Scaffolding}
* @private
*/
this._scaffolding = new Scaffolding(this._templateHelper);
this._setupYargs();
}
/**
* Prepare app and start developer mode HTTP server.
*/
run() {
this._assertApplication();
this._application.ready().then(() => {
this._application.buildCode();
this._application.serve();
});
}
/**
* Build code and exit
*/
buildCode() {
this._assertApplication();
this._application.ready().then(() => {
this._application.buildCode();
process.exit(0);
});
}
/**
* @param {Array<string>} targets Platform names.
*/
build(targets) {
this._assertApplication();
const platformsToCompile = this._application.getPlatforms().filter((platform) =>
targets.includes(platform.getName()) || targets.includes('all')
);
this._application.ready()
.then(() => this._application.compile(platformsToCompile))
.then(process.exit.bind(process, 0), process.exit.bind(process, 1));
}
/**
* @param {string} name
* @param {string} path
*/
addScene(name, path) {
this._assertApplication();
const appName = this._application.getConfig().project.name;
this._scaffolding.addScene(name, appName, path);
process.exit(0);
}
/**
* @param {string} name
* @param {string} path
*/
addPopup(name, path) {
this._assertApplication();
const appName = this._application.getConfig().project.name;
this._scaffolding.addPopup(name, appName, path);
process.exit(0);
}
/**
* @param {string} name
* @param {string} path
*/
addWidget(name, path) {
this._assertApplication();
const appName = this._application.getConfig().project.name;
this._scaffolding.addWidget(name, appName, path);
process.exit(0);
}
/**
* @param {string} target
*/
generateAliases(target) {
this._assertApplication();
this._application.ready().then(() => {
const filename = path.resolve(process.cwd(), target);
const map = {};
for (const [key, value] of this._application.getAliases()) {
map[key] = value;
}
const content = this._templateHelper.render('webpack.config.js.tpl', {map});
fs.writeFile(filename, content, 'utf8', (error) => {
if (error) {
console.error('Error writing aliases map:', error.message);
process.exit(1);
} else {
console.log('Aliases map generated in', chalk.cyan(filename));
process.exit(0);
}
});
});
}
/**
* @return {Yargs}
* @protected
*/
_setupYargs() {
/* eslint-disable newline-per-chained-call */
yargs
.array('config').default('config', [])
.array('addon').default('addon', []);
// @see {https://github.com/yargs/yargs/issues/1336}
const configs = Array.isArray(yargs.argv.config) ?
yargs.argv.config.reduce((flat, value) => flat.concat(value), []) :
[yargs.argv.config];
const app = this._createApplication(configs, yargs.argv.addon);
const buildTargets = ['all'];
let projectConfig = null;
if (app) {
projectConfig = app.getConfig().project;
app.getPlatforms()
.forEach((platform) => {
buildTargets.push(platform.getName());
yargs.command(
platform.getName(),
`${platform.getName()} platform CLI commands`,
(yargs) => platform.buildCLI(yargs, app)
);
});
}
return yargs
.command('run', 'Start development server', (yargs) => this.run(yargs))
.command(
'generateAliases [filename]',
'Generate webpack config file with aliases map',
(yargs) => {
yargs.positional('filename', {
describe: 'file path',
default: 'webpack.config.js'
});
},
(argv) => this.generateAliases(argv.filename)
)
.command('buildCode', 'Generate lifecycle code', (yargs) => this.buildCode(yargs))
.command(
'build <platform..>',
'Build artifact for one or several platforms',
(yargs) => {
yargs.positional('platform', {
describe: 'platform name(s)',
choices: buildTargets
});
},
(argv) => this.build(argv.platform)
)
.command(
'init <name> [root]',
'Initialize ZombieBox application boilerplate',
(yargs) => {
yargs
.positional('name', {
describe: 'project name'
}).positional('root', {
describe: 'root directory',
default: process.cwd()
});
},
(yargv) => this.init(yargv.name, yargv.root)
)
.command(
'addScene <name> [path]',
'Create a Scene boilerplate',
(yargs) => {
yargs
.positional('name', {
describe: 'name'
})
.positional('path', {
describe: 'parent directory',
default: projectConfig ? path.resolve(projectConfig.src, 'scenes') : undefined
});
},
(argv) => this.addScene(argv.name, argv.path)
)
.command(
'addPopup <name> [path]',
'Create a Popup boilerplate',
(yargs) => {
yargs
.positional('name', {
describe: 'name'
})
.positional('path', {
describe: 'parent directory',
default: projectConfig ? path.resolve(projectConfig.src, 'popups') : undefined
});
},
(argv) => this.addPopup(argv.name, argv.path)
)
.command(
'addWidget <name> [path]',
'Create a Widget boilerplate',
(yargs) => {
yargs
.positional('name', {
describe: 'name'
})
.positional('path', {
describe: 'parent directory',
default: projectConfig ? path.resolve(projectConfig.src, 'widgets') : undefined
});
},
(argv) => this.addWidget(argv.name, argv.path)
)
.demandCommand(1, 1, 'No command specified')
.recommendCommands()
.strict(true)
.help()
.argv;
/* eslint-enable newline-per-chained-call */
}
/**
* @param {string} name
* @param {string} root
* @protected
*/
init(name, root) {
const data = {
name
};
this._templateHelper.renderDir('boilerplate', root, data);
console.log(`Boilerplate ZombieBox application ${name} created at`, root);
}
/**
* @param {Object|string} rawConfigs
* @param {string} rawAddons
* @return {?Application}
* @protected
*/
_createApplication(rawConfigs, rawAddons) {
if (this._application) {
return this._application;
}
const packageJson = findPackageJson(
process.cwd(),
(packageJson) => packageJson.dependencies && packageJson.dependencies.hasOwnProperty('zombiebox')
);
if (!packageJson) {
return null;
}
const configs = rawConfigs.map((fileOrObject) => {
if (typeof fileOrObject === 'string') {
return path.resolve(fileOrObject);
}
return fileOrObject;
});
const addons = rawAddons.map((file) => path.resolve(file));
try {
this._application = new Application(path.dirname(packageJson), configs, addons);
} catch (e) {
console.error(e.message);
console.warn('Failed to create ZombieBox Application instance. CLI functionality will be limited');
}
return this._application;
}
/**
* @protected
*/
_assertApplication() {
if (!this._application) {
console.error('Could not find ZombieBox applicaion');
process.exit(1);
}
}
}
/**
* @typedef {{
* argv: Array<*>
* }}
*/
let Yargs;
new CLI();