yunkong2
Version:
automate your life - platfom
620 lines (583 loc) • 27 kB
JavaScript
// @ts-check
;
const fs = require('fs-extra');
const semver = require('semver');
const path = require('path');
let request;
let extend;
function rmdirRecursiveSync(path) {
if (fs.existsSync(path)) {
fs.readdirSync(path).forEach(function (file, index) {
const curPath = path + '/' + file;
if (fs.statSync(curPath).isDirectory()) {
// recurse
rmdirRecursiveSync(curPath);
} else {
// delete file
fs.unlinkSync(curPath);
}
});
// delete (hopefully) empty folder
try {
fs.rmdirSync(path);
} catch (e) {
console.log('Cannot delete directory ' + path + ': ' + e.toString());
}
}
}
function findIPs() {
const ifaces = require('os').networkInterfaces();
const ipArr = [];
for (const dev in ifaces) {
if (ifaces.hasOwnProperty(dev)) {
/*jshint loopfunc:true */
ifaces[dev].forEach(function (details) {
if (!details.internal) ipArr.push(details.address);
});
}
}
return ipArr;
}
function findPath(path, url) {
if (!url) return '';
if (url.substring(0, 'http://'.length) === 'http://' ||
url.substring(0, 'https://'.length) === 'https://') {
return url;
} else {
if (path.substring(0, 'http://'.length) === 'http://' ||
path.substring(0, 'https://'.length) === 'https://') {
return (path + url).replace(/\/\//g, '/').replace('http:/', 'http://').replace('https:/', 'https://');
} else {
if (url && url[0] === '/') {
return __dirname + '/..' + url;
} else {
return __dirname + '/../' + path + url;
}
}
}
}
// Download file to tmp or return file name directly
function getFile(urlOrPath, fileName, callback) {
if (!request) request = require('request');
// If object was read
if (urlOrPath.substring(0, 'http://'.length) === 'http://' ||
urlOrPath.substring(0, 'https://'.length) === 'https://') {
const tmpFile = __dirname + '/../tmp/' + (fileName || Math.floor(Math.random() * 0xFFFFFFE) + '.zip');
request(urlOrPath).on('error', function (/* error */) {
console.log('Cannot download ' + tmpFile);
if (callback) callback(tmpFile);
}).pipe(fs.createWriteStream(tmpFile)).on('close', function () {
console.log('downloaded ' + tmpFile);
if (callback) callback(tmpFile);
});
} else {
if (fs.existsSync(urlOrPath)) {
if (callback) callback(urlOrPath);
} else if (fs.existsSync(__dirname + '/../' + urlOrPath)) {
if (callback) callback(__dirname + '/../' + urlOrPath);
} else if (fs.existsSync(__dirname + '/../tmp/' + urlOrPath)) {
if (callback) callback(__dirname + '/../tmp/' + urlOrPath);
} else if (fs.existsSync(__dirname + '/../adapter/' + urlOrPath)) {
if (callback) callback(__dirname + '/../adapter/' + urlOrPath);
} else {
console.log('File not found: ' + urlOrPath);
process.exit(1);
}
}
}
// Return content of the json file. Download it or read directly
function getJson(urlOrPath, callback) {
if (!request) request = require('request');
let sources = {};
// If object was read
if (urlOrPath && typeof urlOrPath === 'object') {
if (callback) callback(urlOrPath);
} else if (!urlOrPath) {
console.log('Empty url!');
if (callback) callback(null);
} else {
if (urlOrPath.substring(0, 'http://'.length) === 'http://' ||
urlOrPath.substring(0, 'https://'.length) === 'https://') {
request({ url: urlOrPath, timeout: 5000 }, function (error, response, body) {
if (error || !body || response.statusCode !== 200) {
console.log('Cannot download json from ' + urlOrPath + '. Error: ' + (error || body));
if (callback) callback(null, urlOrPath);
return;
}
try {
sources = JSON.parse(body);
} catch (e) {
console.log('Json file is invalid on ' + urlOrPath);
if (callback) callback(null, urlOrPath);
return;
}
if (callback) callback(sources, urlOrPath);
}).on('error', function (error) {
//console.log('Cannot download json from ' + urlOrPath + '. Error: ' + error);
//if (callback) callback(null, urlOrPath);
});
} else {
if (fs.existsSync(urlOrPath)) {
try {
sources = JSON.parse(fs.readFileSync(urlOrPath, 'utf8'));
} catch (e) {
console.log('Cannot parse json file from ' + urlOrPath + '. Error: ' + e);
if (callback) callback(null, urlOrPath);
return;
}
if (callback) callback(sources, urlOrPath);
} else if (fs.existsSync(__dirname + '/../' + urlOrPath)) {
try {
sources = JSON.parse(fs.readFileSync(__dirname + '/../' + urlOrPath, 'utf8'));
} catch (e) {
console.log('Cannot parse json file from ' + __dirname + '/../' + urlOrPath + '. Error: ' + e);
if (callback) callback(null, urlOrPath);
return;
}
if (callback) callback(sources, urlOrPath);
} else if (fs.existsSync(__dirname + '/../tmp/' + urlOrPath)) {
try {
sources = JSON.parse(fs.readFileSync(__dirname + '/../tmp/' + urlOrPath, 'utf8'));
} catch (e) {
console.log('Cannot parse json file from ' + __dirname + '/../tmp/' + urlOrPath + '. Error: ' + e);
if (callback) callback(null, urlOrPath);
return;
}
if (callback) callback(sources, urlOrPath);
} else if (fs.existsSync(__dirname + '/../adapter/' + urlOrPath)) {
try {
sources = JSON.parse(fs.readFileSync(__dirname + '/../adapter/' + urlOrPath, 'utf8'));
} catch (e) {
console.log('Cannot parse json file from ' + __dirname + '/../adapter/' + urlOrPath + '. Error: ' + e);
if (callback) callback(null, urlOrPath);
return;
}
if (callback) callback(sources, urlOrPath);
} else {
//if (urlOrPath.indexOf('/example/') === -1) console.log('Json file not found: ' + urlOrPath);
if (callback) callback(null, urlOrPath);
}
}
}
}
// Get list of all installed adapters and controller version on this host
function getInstalledInfo(hostRunningVersion) {
const result = {};
let path = __dirname + '/../';
// Get info about host
let ioPackage = JSON.parse(fs.readFileSync(path + 'io-package.json', 'utf8'));
let pack = fs.existsSync(path + 'package.json') ? JSON.parse(fs.readFileSync(path + 'package.json', 'utf8')) : {};
result[ioPackage.common.name] = {
controller: true,
version: ioPackage.common.version,
icon: ioPackage.common.extIcon || ioPackage.common.icon,
title: ioPackage.common.title,
desc: ioPackage.common.desc,
platform: ioPackage.common.platform,
keywords: ioPackage.common.keywords,
readme: ioPackage.common.readme,
runningVersion: hostRunningVersion,
license: ioPackage.common.license ? ioPackage.common.license : ((pack.licenses && pack.licenses.length) ? pack.licenses[0].type : ''),
licenseUrl: (pack.licenses && pack.licenses.length) ? pack.licenses[0].url : ''
};
let dirs = fs.readdirSync(__dirname + '/../adapter');
for (let i = 0; i < dirs.length; i++) {
try {
path = __dirname + '/../adapter/' + dirs[i] + '/';
if (fs.existsSync(path + 'io-package.json')) {
ioPackage = JSON.parse(fs.readFileSync(path + 'io-package.json', 'utf8'));
pack = fs.existsSync(path + 'package.json') ? JSON.parse(fs.readFileSync(path + 'package.json', 'utf8')) : {};
result[ioPackage.common.name] = {
controller: false,
version: ioPackage.common.version,
icon: ioPackage.common.extIcon || (ioPackage.common.icon ? '/adapter/' + dirs[i] + '/' + ioPackage.common.icon : ''),
title: ioPackage.common.title,
desc: ioPackage.common.desc,
platform: ioPackage.common.platform,
keywords: ioPackage.common.keywords,
readme: ioPackage.common.readme,
type: ioPackage.common.type,
license: ioPackage.common.license ? ioPackage.common.license : ((pack.licenses && pack.licenses.length) ? pack.licenses[0].type : ''),
licenseUrl: (pack.licenses && pack.licenses.length) ? pack.licenses[0].url : ''
};
}
} catch (e) {
console.log('Cannot read or parse ' + __dirname + '/../adapter/' + dirs[i] + '/io-package.json: ' + e.toString());
}
}
dirs = fs.readdirSync(__dirname + '/../node_modules');
for (let i = 0; i < dirs.length; i++) {
try {
path = __dirname + '/../node_modules/' + dirs[i] + '/';
if (dirs[i].match(/^yunkong2\./i) && fs.existsSync(path + 'io-package.json')) {
ioPackage = JSON.parse(fs.readFileSync(path + 'io-package.json', 'utf8'));
pack = fs.existsSync(path + 'package.json') ? JSON.parse(fs.readFileSync(path + 'package.json', 'utf8')) : {};
result[ioPackage.common.name] = {
controller: false,
version: ioPackage.common.version,
icon: ioPackage.common.extIcon || (ioPackage.common.icon ? '/adapter/' + dirs[i] + '/' + ioPackage.common.icon : ''),
title: ioPackage.common.title,
desc: ioPackage.common.desc,
platform: ioPackage.common.platform,
keywords: ioPackage.common.keywords,
readme: ioPackage.common.readme,
type: ioPackage.common.type,
license: ioPackage.common.license ? ioPackage.common.license : ((pack.licenses && pack.licenses.length) ? pack.licenses[0].type : ''),
licenseUrl: (pack.licenses && pack.licenses.length) ? pack.licenses[0].url : ''
};
}
} catch (e) {
console.log('Cannot read or parse ' + __dirname + '/../node_modules/' + dirs[i] + '/io-package.json: ' + e.toString());
}
}
if (fs.existsSync(__dirname + '/../../../node_modules/yunkong2.js-controller') ||
fs.existsSync(__dirname + '/../../../node_modules/yunkong2.js-controller')) {
dirs = fs.readdirSync(__dirname + '/../..');
for (let i = 0; i < dirs.length; i++) {
try {
path = __dirname + '/../../' + dirs[i] + '/';
if (dirs[i].match(/^yunkong2\./i) && dirs[i].substring('yunkong2.'.length) !== 'js-controller' &&
fs.existsSync(path + 'io-package.json')) {
ioPackage = JSON.parse(fs.readFileSync(path + 'io-package.json', 'utf8'));
pack = fs.existsSync(path + 'package.json') ? JSON.parse(fs.readFileSync(path + 'package.json', 'utf8')) : {};
result[ioPackage.common.name] = {
controller: false,
version: ioPackage.common.version,
icon: ioPackage.common.extIcon || (ioPackage.common.icon ? '/adapter/' + dirs[i] + '/' + ioPackage.common.icon : ''),
title: ioPackage.common.title,
desc: ioPackage.common.desc,
platform: ioPackage.common.platform,
keywords: ioPackage.common.keywords,
readme: ioPackage.common.readme,
license: ioPackage.common.license ? ioPackage.common.license : ((pack.licenses && pack.licenses.length) ? pack.licenses[0].type : ''),
licenseUrl: (pack.licenses && pack.licenses.length) ? pack.licenses[0].url : ''
};
}
} catch (e) {
console.log('Cannot read or parse ' + __dirname + '/../node_modules/' + dirs[i] + '/io-package.json: ' + e.toString());
}
}
}
return result;
}
/**
* Reads an adapter's npm version
* @param {string | null} adapter The adapter to read the npm version from. Null for the root yunkong2 packet
* @param {(err: Error | null, version?: string) => void} [callback]
*/
function getNpmVersion(adapter, callback) {
adapter = adapter ? 'yunkong2.' + adapter : 'yunkong2';
adapter = adapter.toLowerCase();
const cliCommand = `npm view ${adapter}@latest version`;
const exec = require('child_process').exec;
exec(cliCommand, { timeout: 2000 }, (error, stdout, stderr) => {
let version;
if (error) {
// command failed
if (typeof callback === 'function') {
callback(error);
return;
}
} else if (stdout) {
version = semver.valid(stdout.trim());
}
if (typeof callback === 'function') callback(null, version);
});
}
function getIoPack(sources, name, callback) {
getJson(sources[name].meta, function (ioPack) {
const packUrl = sources[name].meta.replace('io-package.json', 'package.json');
getJson(packUrl, function (pack) {
// If installed from git or something else
// js-controller is exception, because can be installed from npm and from git
if (sources[name].url && name !== 'js-controller') {
if (ioPack && ioPack.common) {
sources[name] = extend(true, sources[name], ioPack.common);
if (pack && pack.licenses && pack.licenses.length) {
if (!sources[name].license) sources[name].license = pack.licenses[0].type;
if (!sources[name].licenseUrl) sources[name].licenseUrl = pack.licenses[0].url;
}
}
if (callback) callback(sources, name);
} else {
if (ioPack && ioPack.common) {
sources[name] = extend(true, sources[name], ioPack.common);
if (pack && pack.licenses && pack.licenses.length) {
if (!sources[name].license) sources[name].license = pack.licenses[0].type;
if (!sources[name].licenseUrl) sources[name].licenseUrl = pack.licenses[0].url;
}
}
if (sources[name].meta.substring(0, 'http://'.length) === 'http://' ||
sources[name].meta.substring(0, 'https://'.length) === 'https://') {
//installed from npm
getNpmVersion(name, function (err, version) {
if (version) sources[name].version = version;
if (callback) callback(sources, name);
});
} else {
if (callback) callback(sources, name);
}
}
});
});
}
// Get list of all adapters and controller in some repository file or in /conf/source-dist.json
function getRepositoryFile(urlOrPath, callback) {
let sources = {};
let path = '';
let toRead = 0;
let timeout = null;
let count = 0;
if (!extend) extend = require('node.extend');
if (urlOrPath) {
const parts = urlOrPath.split('/');
path = parts.splice(0, parts.length - 1).join('/') + '/';
}
// If object was read
if (urlOrPath && typeof urlOrPath === 'object') {
if (callback) callback(urlOrPath);
} else if (!urlOrPath) {
try {
sources = JSON.parse(fs.readFileSync(__dirname + '/../conf/sources.json', 'utf8'));
} catch (e) {
sources = {};
}
try {
const sourcesDist = JSON.parse(fs.readFileSync(__dirname + '/../conf/sources-dist.json', 'utf8'));
sources = extend(true, sourcesDist, sources);
} catch (e) {
// Don't care
}
for (const name in sources) {
if (sources[name].url) sources[name].url = findPath(path, sources[name].url);
if (sources[name].meta) sources[name].meta = findPath(path, sources[name].meta);
if (sources[name].icon) sources[name].icon = findPath(path, sources[name].icon);
if (!sources[name].version && sources[name].meta) {
toRead++;
count++;
getIoPack(sources, name, function (ignore, name) {
toRead--;
if (!toRead && timeout) {
clearTimeout(timeout);
if (callback) callback(sources);
timeout = null;
callback = null;
}
});
}
}
if (!toRead) {
if (callback) callback(sources);
} else {
timeout = setTimeout(function () {
if (timeout) {
console.log('Timeout by read all package.json (' + count + ') seconds');
clearTimeout(timeout);
if (callback) callback(sources);
timeout = null;
callback = null;
}
}, count * 500);
}
} else {
getJson(urlOrPath, function (sources) {
if (sources) {
for (const name in sources) {
if (!sources.hasOwnProperty(name)) continue;
if (sources[name].url) sources[name].url = findPath(path, sources[name].url);
if (sources[name].meta) sources[name].meta = findPath(path, sources[name].meta);
if (sources[name].icon) sources[name].icon = findPath(path, sources[name].icon);
if (!sources[name].version && sources[name].meta) {
toRead++;
count++;
getIoPack(sources, name, function (ignore, name) {
toRead--;
if (!toRead && timeout) {
clearTimeout(timeout);
if (callback) callback(sources);
timeout = null;
callback = null;
}
});
}
}
}
if (!toRead) {
if (callback) callback(sources);
} else {
timeout = setTimeout(function () {
if (timeout) {
console.log('Timeout by read all package.json (' + count + ') seconds');
clearTimeout(timeout);
if (callback) callback(sources);
timeout = null;
callback = null;
}
}, count * 500);
}
});
}
}
function sendDiagInfo(obj, callback) {
if (!request) request = require('request');
request.post({
url: 'http://download.yunkong2.org/diag.php',
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: 'data=' + JSON.stringify(obj),
timeout: 2000
}, function (err, response, body) {
/*if (err || !body || response.statusCode !== 200) {
}*/
}).on('error', function (error) {
console.log('Cannot send diag info: ' + error.message);
});
}
function getAdapterDir(adapter, isNpm) {
const parts = __dirname.replace(/\\/g, '/').split('/');
parts.splice(parts.length - 3, 3);
/** @type {string | string[]} */
let dir = parts.join('/');
if (adapter.substring(0, 'yunkong2.'.length) === 'yunkong2.') adapter = adapter.substring('yunkong2.'.length);
if (fs.existsSync(dir + '/node_modules/yunkong2.js-controller') &&
fs.existsSync(dir + '/node_modules/yunkong2.' + adapter)) {
dir = __dirname.replace(/\\/g, '/').split('/');
dir.splice(dir.length - 2, 2);
return dir.join('/') + '/yunkong2.' + adapter;
} else if (fs.existsSync(__dirname + '/../node_modules/yunkong2.' + adapter)) {
dir = __dirname.replace(/\\/g, '/').split('/');
dir.splice(dir.length - 1, 1);
return dir.join('/') + '/node_modules/yunkong2.' + adapter;
} else if (fs.existsSync(__dirname + '/../adapter/' + adapter)) {
dir = __dirname.replace(/\\/g, '/').split('/');
dir.splice(dir.length - 1, 1);
return dir.join('/') + '/adapter/' + adapter;
} else {
if (isNpm) {
if (fs.existsSync(__dirname + '/../../node_modules/yunkong2.js-controller')) {
dir = __dirname.replace(/\\/g, '/').split('/');
dir.splice(dir.length - 2, 2);
return dir.join('/') + '/yunkong2.' + adapter;
} else {
dir = __dirname.replace(/\\/g, '/').split('/');
dir.splice(dir.length - 1, 1);
return dir.join('/') + '/node_modules/yunkong2.' + adapter;
}
} else {
dir = __dirname.replace(/\\/g, '/').split('/');
dir.splice(dir.length - 1, 1);
return dir.join('/') + '/adapter/' + adapter;
}
}
}
// All pathes are returned always relative to /node_modules/yunkong2.js-controller
function getDefaultDataDir() {
/** @type {string | string[]} */
let dataDir = __dirname.replace(/\\/g, '/');
dataDir = dataDir.split('/');
// If installed with npm
if (fs.existsSync(__dirname + '/../../../node_modules/yunkong2.js-controller')) {
return '../../yunkong2-data/';
} else {
dataDir.splice(dataDir.length - 1, 1);
dataDir = dataDir.join('/');
return './data/';
}
}
function getConfigFileName() {
/** @type {string | string[]} */
let configDir = __dirname.replace(/\\/g, '/');
configDir = configDir.split('/');
// If installed with npm
if (fs.existsSync(__dirname + '/../../../node_modules/yunkong2.js-controller') ||
fs.existsSync(__dirname + '/../../../node_modules/yunkong2.js-controller')) {
// remove /node_modules/yunkong2.js-controller/lib
configDir.splice(configDir.length - 3, 3);
configDir = configDir.join('/');
return configDir + '/yunkong2-data/yunkong2.json';
} else {
// Remove /lib
configDir.splice(configDir.length - 1, 1);
configDir = configDir.join('/');
if (fs.existsSync(__dirname + '/../conf/yunkong2.json')) {
return configDir + '/conf/yunkong2.json';
} else {
return configDir + '/data/yunkong2.json';
}
}
}
/**
* Tests if we are currently inside a node_modules folder
* @returns {boolean}
*/
function isThisInsideNodeModules() {
return /[\\/]node_modules[\\/]/.test(__dirname);
}
/**
* Recursively enumerates all files in the given directory
* @param {string} dir The directory to scan
* @param {(name: string) => boolean} [predicate] An optional predicate to apply to every found file system entry
* @returns {string[]} A list of all files found
*/
function enumFilesRecursiveSync(dir, predicate) {
const ret = [];
if (typeof predicate !== 'function') predicate = () => true;
// enumerate all files in this directory
const filesOrDirs = fs.readdirSync(dir)
.filter(predicate) // exclude all files starting with "."
.map(f => path.join(dir, f)) // and prepend the full path
;
for (const entry of filesOrDirs) {
if (fs.statSync(entry).isDirectory()) {
// Continue recursing this directory and remember the files there
Array.prototype.push.apply(ret, enumFilesRecursiveSync(entry, predicate));
} else {
// remember this file
ret.push(entry);
}
}
return ret;
}
/**
* Recursively copies all files from the source to the target directory
* @param {string} sourceDir The directory to scan
* @param {string} targetDir The directory to copy to
* @param {(name: string) => boolean} [predicate] An optional predicate to apply to every found file system entry
*/
function copyFilesRecursiveSync(sourceDir, targetDir, predicate) {
// Enumerate all files in this module that are supposed to be in the root directory
const filesToCopy = enumFilesRecursiveSync(sourceDir, predicate);
// Copy all of them to the corresponding target dir
for (const file of filesToCopy) {
// Find out where it's supposed to be
const targetFileName = path.join(targetDir, path.relative(sourceDir, file));
// Ensure the directory exists
fs.ensureDirSync(path.dirname(targetFileName));
// And copy the file
fs.copySync(file, targetFileName);
}
}
/** Checks if this installation process is automated */
function isAutomatedInstallation() {
const possibilities = [
path.join(__dirname, '..', 'AUTOMATED_INSTALLER'),
path.join(__dirname, '..', '../..', 'AUTOMATED_INSTALLER')
];
return possibilities.some(f => fs.existsSync(f));
}
module.exports = {
findIPs,
rmdirRecursiveSync,
getRepositoryFile,
getFile,
getJson,
getInstalledInfo,
sendDiagInfo,
getAdapterDir,
getDefaultDataDir,
getConfigFileName,
isThisInsideNodeModules,
enumFilesRecursiveSync,
copyFilesRecursiveSync,
isAutomatedInstallation
};