@webos-tools/cli
Version:
Command Line Interface for development webOS application and service
408 lines (379 loc) • 15.9 kB
JavaScript
/*
* Copyright (c) 2020-2024 LG Electronics Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
const promise = require('bluebird'),
Table = require('easy-table'),
fs = promise.promisifyAll(require('fs-extra')),
log = require('npmlog'),
path = require('path'),
errHndl = require('./base/error-handler'),
copyToDirAsync = require('./util/copy').copyToDirAsync,
readJsonSync = require('./util/json').readJsonSync,
merge = require('./util/merge');
const templatePath = path.join(__dirname, '/../files/conf/', 'template.json');
let templates;
log.heading = 'generator';
log.level = 'warn';
function Generator() {
if (templatePath) {
const cliPath = path.join(__dirname, '..');
let contents = fs.readFileSync(templatePath);
contents = contents.toString().replace(/\$cli-root/gi, cliPath).replace(/\\/g,'/');
templates = JSON.parse(contents);
} else {
templates = null;
}
}
Generator.prototype.showTemplates = function(listType, next) {
const templateList = this.getTmpl(),
table = new Table(),
_displayType = {
"webapp": "Web App",
"nativeapp": "Native App",
"webappinfo": "Web App Info",
"nativeappinfo": "Native App Info",
"jsservice": "JS Service",
"nativeservice": "Native Service",
"jsserviceinfo": "JS Service Info",
"nativeserviceinfo": "Native Service Info",
"icon": "Icon",
"library": "Library",
"packageinfo": "Package Info",
"qmlapp": "QML App",
"qmlappinfo": "QML App Info"
};
for (const name in templateList) {
if (templateList[name].hide === true || !templateList[name].type) {
continue;
}
const isDefault = (templateList[name].default) ? "(default) " : "",
type = _displayType[templateList[name].type] || templateList[name].type;
if (listType && ["true", "false", true, false].indexOf(listType) === -1) {
if (templateList[name].type &&
(templateList[name].type.match(new RegExp(listType+"$","gi")) === null)) {
continue;
}
}
table.cell('ID', name);
table.cell('Project Type', type);
table.cell('Description', isDefault + templateList[name].description);
table.newRow();
}
return next(null, {msg: table.toString()});
};
Generator.prototype.generate = function(options, next) {
// For API
if (!options.tmplName) {
return next(errHndl.getErrMsg("EMPTY_VALUE", "TEMPLATE"));
}
if (!options.out) {
return next(errHndl.getErrMsg("EMPTY_VALUE", "APP_DIR"));
}
const tmplName = options.tmplName,
pkginfo = options.pkginfo || {},
svcinfo = options.svcinfo || {},
svcName = options.svcName,
out = options.out,
dest = path.resolve(out),
existDir = this.existOutDir(dest),
templateList = this.getTmpl(),
template = templateList[tmplName];
let appinfo = options.appinfo || {};
// For API
if (!template) {
return next(errHndl.getErrMsg("INVALID_VALUE", "TEMPLATE", options.tmplName));
}
if (!options.overwrite && existDir) {
return next(errHndl.getErrMsg("NOT_OVERWRITE_DIR", dest));
}
if (template.metadata && template.metadata.data && typeof template.metadata.data === 'object') {
appinfo = merge(appinfo, template.metadata.data);
}
promise.resolve()
.then(function() {
// If props is not exist, this input from query-mode
// If props is exist, this input from props
// Check argv.query, argv["no-query"], options.props and conditional statement.
if (template.type.match(/(app$|appinfo$)/)) {
parsePropArgs(options.props, appinfo);
} else if (template.type.match(/(service$|serviceinfo$)/)) {
parsePropArgs(options.props, svcinfo);
} else if (template.type.match(/(package$|packageinfo$)/)) {
parsePropArgs(options.props, pkginfo);
}
})
.then(function() {
if (svcName) {
svcinfo.id = svcName;
svcinfo.services = [{
"name": svcName
}];
} else if (!svcName && svcinfo && !!svcinfo.id) {
svcinfo.services = [{
"name": svcinfo.id
}];
}
});
return promise.resolve()
.then(function() {
log.info("generator#generate()", "template name:" + tmplName);
next(null, {msg: "Generating " + tmplName + " in " + dest});
let srcs;
if (tmplName.match(/(^hosted)/)) {
srcs = [].concat(template.path);
return promise.all(srcs.map(function(src) {
return copyToDirAsync(src, dest);
})).then(function() {
let metaTmpl;
let url;
if (template.metadata && template.metadata.id) {
metaTmpl = templateList[template.metadata.id];
}
if (metaTmpl) {
if (appinfo.url) {
url = appinfo.url;
delete appinfo.url;
const urlTmpl = {"path":path.join(srcs[0],'index.html')};
_writeURLdata(urlTmpl, url);
}
return _writeMetadata(metaTmpl, appinfo, svcinfo, pkginfo);
} else {
return;
}
});
} else if (tmplName.match(/(^qmlapp$)/)) {
srcs = [].concat(template.path);
return promise.all(srcs.map(function(src) {
return copyToDirAsync(src, dest);
})).then(function() {
let metaTmpl;
if (template.metadata && template.metadata.id) {
metaTmpl = templateList[template.metadata.id];
}
if (metaTmpl) {
if (appinfo.id) {
const qmlTmpl = {"path":path.join(srcs[0],'main.qml')};
_writeAppIDdata(qmlTmpl, appinfo.id);
}
return _writeMetadata(metaTmpl, appinfo, svcinfo, pkginfo);
} else {
return;
}
});
} else if (tmplName.match(/(^native_service$)/)) {
srcs = [].concat(template.path);
return promise.all(srcs.map(function(src) {
return copyToDirAsync(src, dest);
})).then(function() {
if(svcinfo.id){
const serviceId = svcinfo.id;
const appId = _appIdFromServiceId(serviceId);
//Replace app id and service id
const mainFilePath = path.join(dest,'src','main.c');
let mainFile = fs.readFileSync(mainFilePath, 'utf8');
mainFile = mainFile.replace(/SERVICE_ID/g, serviceId).replace(/APP_ID/g, appId);
fs.writeFileSync(mainFilePath, mainFile, {encoding: 'utf8'});
//Replace the default service id
const serviceInfoFile = path.join(dest,"services.json");
const serviceInfo = readJsonSync(serviceInfoFile)
serviceInfo.id = serviceId;
if(serviceInfo.services && Array.isArray(serviceInfo.services)){
serviceInfo.services[0]['name'] = serviceId;
}
fs.writeFileSync(serviceInfoFile, JSON.stringify(serviceInfo, null, 2));
}
});
} else if (template.type.match(/info$/)) {
return _writeMetadata(template, appinfo, svcinfo, pkginfo);
} else {
srcs = [].concat(template.path);
return promise.all(srcs.map(function(src) {
log.info("generator#generate()", "template src:" + src);
return copyToDirAsync(src, dest);
})).then(function() {
let metaTmpl;
if (template.metadata && template.metadata.id) {
metaTmpl = templateList[template.metadata.id];
}
if (metaTmpl) {
return _writeMetadata(metaTmpl, appinfo, svcinfo, pkginfo);
} else {
return;
}
});
}
})
.then(function() {
const deps = templateList[tmplName].deps || [];
return promise.all(deps.map(function(dep) {
if (!templateList[dep]) {
log.warn("generator#generate()", "Invalid template id:" + dep);
return;
} else if (!templateList[dep].path) {
log.warn("generator#generate()", "Invalid template path:" + dep);
return;
}
return copyToDirAsync(templateList[dep].path, dest);
}));
})
.then(function() {
log.info("generator#generate()", "done");
return next(null, {
msg: "Success"
});
})
.catch(function(err) {
log.silly("generator#generate()", "err:", err);
throw err;
});
function _writeAppIDdata(qmlTmpl, appId) {
const filePaths = [].concat(qmlTmpl.path);
return promise.all(filePaths.map(function(file) {
return fs.lstatAsync(file)
.then(function(stats) {
if (!stats.isFile()) {
throw errHndl.getErrMsg("INVALID_PATH", "meta template", file);
}
// eslint-disable-next-line no-useless-escape
const exp = /appId\s*:\s*[\'\"][\w.]*[\'\"]/g;
const destFile = path.join(dest, path.basename(file));
let qmlFile = fs.readFileSync(file, 'utf8');
qmlFile = qmlFile.replace(exp, "appId: \"" + appId + "\"");
fs.writeFileSync(destFile, qmlFile, {encoding: 'utf8'});
});
}))
.then(function() {
log.info("generator#generate()#_writeAppIDdata()", "done");
return;
})
.catch(function(err) {
log.silly("generator#generate()#_writeAppIDdata()", "err:", err);
throw err;
});
}
function _writeURLdata(urlTmpl, url) {
const filePaths = [].concat(urlTmpl.path);
return promise.all(filePaths.map(function(file) {
return fs.lstatAsync(file)
.then(function(stats) {
if (!stats.isFile()) {
throw errHndl.getErrMsg("INVALID_PATH", "meta template", file);
}
let html = fs.readFileSync(file, 'utf8');
// eslint-disable-next-line no-useless-escape
const exp = new RegExp("(?:[\'\"])([\:/.A-z?<_&\s=>0-9;-]+\')");
// eslint-disable-next-line no-useless-escape
html = html.replace(exp, "\'" + url + "\'");
const destFile = path.join(dest, path.basename(file));
fs.writeFileSync(destFile, html, {encoding: 'utf8'});
});
}))
.then(function() {
log.info("generator#generate()#_writeURLdata()", "done");
return;
})
.catch(function(err) {
log.silly("generator#generate()#_writeURLdata()", "err:", err);
throw err;
});
}
function _appIdFromServiceId(serviceId){
const svcIdInArray = serviceId.split(".");
if(svcIdInArray.length === 1) return serviceId;
return svcIdInArray.slice(0,svcIdInArray.length -1).join(".");
}
function _writeMetadata(metaTmpl, _appinfo, _svcinfo, _pkginfo) {
const metaPaths = [].concat(metaTmpl.path),
appInfo = _appinfo || {},
svcInfo = _svcinfo || {},
pkgInfo = _pkginfo || {};
return promise.all(metaPaths.map(function(file) {
return fs.lstatAsync(file)
.then(function(stats) {
if (!stats.isFile()) {
throw errHndl.getErrMsg("INVALID_PATH", "meta template", file);
}
const fileName = path.basename(file);
let info = readJsonSync(file);
if (fileName === 'appinfo.json') {
info = merge(info, appInfo);
} else if (fileName === "services.json") {
info = merge(info, svcInfo);
} else if (fileName === "package.json" &&
(metaTmpl.type === "jsserviceinfo" || metaTmpl.type === "nativeserviceinfo")) {
info.name = svcInfo.id || info.name;
} else if (fileName === "packageinfo.json") {
info = merge(info, pkgInfo);
}
return info;
})
.then(function(info) {
const destFile = path.join(dest, path.basename(file));
return fs.mkdirsAsync(dest)
.then(function() {
return fs.writeFileSync(destFile, JSON.stringify(info, null, 2), {
encoding: 'utf8'
});
});
});
}))
.then(function() {
log.info("generator#generate()#_writeMetadata()", "done");
return;
})
.catch(function(err) {
log.silly("generator#generate()#_writeMetadata()", "err:", err);
throw err;
});
}
};
Generator.prototype.getTmpl = function() {
return templates;
};
Generator.prototype.existOutDir = function(outDir) {
log.verbose("generator#existOutDir()", outDir);
try {
const files = fs.readdirSync(outDir);
if (files.length > 0)
return true;
} catch (err) {
if (err && err.code === 'ENOTDIR') {
throw errHndl.getErrMsg("NOT_DIRTYPE_PATH", outDir);
}
if (err && err.code === 'ENOENT') {
log.verbose("generator#generate()", "The directory does not exist.");
return false;
}
throw err;
}
};
// Internal functions
function parsePropArgs(property, targetInfo) {
const props = property || [],
info = targetInfo || {};
if (props.length === 1 && props[0].indexOf('{') !== -1 && props[0].indexOf('}') !== -1 &&
((props[0].split("'").length - 1) % 2) === 0)
{
// eslint-disable-next-line no-useless-escape
props[0] = props[0].replace(/\'/g,'"');
}
props.forEach(function(prop) {
try {
const data = JSON.parse(prop);
for (const k in data) {
info[k] = data[k];
}
} catch (err) {
const tokens = prop.split('=');
if (tokens.length === 2) {
info[tokens[0]] = tokens[1];
} else {
log.warn('Ignoring invalid arguments:', prop);
}
}
});
}
module.exports = Generator;