import os from 'os' import path from 'path' import fs from 'fs/promises' import { spawn, execSync } from 'child_process' import { fileURLToPath, pathToFileURL } from 'url' export default class Util { static combineStack(err, appendErr) { err.stack = err.stack + '\nFrom:\n' + appendErr.stack.split('\n').slice(1).join('\n') + '\n' return err } constructor(root_import_meta_url) { this._root_import_meta_url = root_import_meta_url } getPathFromRoot(add) { const __dirname = path.dirname(fileURLToPath(this._root_import_meta_url)); return path.join(__dirname,'./', add) } getUrlFromRoot(add) { return path.join(this._root_import_meta_url,'../', add) } getExtension(filename) { let extension = path.extname(filename) if (filename.indexOf('.tar.') > 0) { return '.tar' + extension } return extension } getAppNames(config) { const validLevels = [ 'fatal', 'error', 'warn', 'info', 'debug', 'trace', ] let out = [] let keys = Object.keys(config) for (let key of keys) { if (typeof(config[key]) !== 'object' || config[key] == null) continue if (typeof(config[key].port) !== 'number' || !config[key].port) continue if (typeof(config[key].provider) !== 'string' || !config[key].provider) continue if (config[key].https != null && typeof(config[key].https) !== 'boolean') continue if (config[key].updateEvery != null && (typeof(config[key].updateEvery) !== 'number' || config[key].updateEvery < 0)) continue if (config[key].startWaitUntilFail != null && (typeof(config[key].startWaitUntilFail) !== 'number' || config[key].startWaitUntilFail < 10)) continue if (config[key].heartbeatTimeout != null && (typeof(config[key].heartbeatTimeout) !== 'number' || config[key].heartbeatTimeout < 10)) continue if (config[key].heartbeatAttempts != null && (typeof(config[key].heartbeatAttempts) !== 'number' || config[key].heartbeatAttempts < 1)) continue if (config[key].heartbeatAttemptsWait != null && (typeof(config[key].heartbeatAttemptsWait) !== 'number' || config[key].heartbeatAttemptsWait < 10)) continue if (config[key].heartbeatPath != null && (typeof(config[key].heartbeatPath) !== 'string' || config[key].heartbeatPath[0] !== '/')) continue if (config[key].log != null) { if (!Array.isArray(config[key].log)) continue let valid = true for (let stream of config[key].log) { if (!stream || typeof(stream) !== 'object' || Array.isArray(stream)) { valid = false break } if (typeof(stream.level) !== 'string' || !stream.level || !validLevels.includes(stream.level)) { valid = false break } if ((typeof(stream.path) !== 'string' || !stream.path) && stream.stream !== 'process.stdout') { valid = false break } } if (!valid) continue } if (config[key].cluster != null && (typeof(config[key].cluster) !== 'number' || config[key].cluster > 100 || config[key].cluster < 0)) continue if (config[key].clusterWaitOnCrash != null && (typeof(config[key].clusterWaitOnCrash) !== 'number' || config[key].clusterWaitOnCrash < 10)) continue out.push(key) } return out } get7zipExecutable() { const util = new Util(import.meta.url) if (process.platform === 'win32') { return util.getPathFromRoot('../bin/7zdec.exe') } return util.getPathFromRoot('../bin/7zdec') } getNpmExecutable() { if (process.platform === 'win32') { return 'npm.cmd' } return 'npm' } verifyConfig(config) { if (!config.name) throw new Error('name is missing in config.json') if (this.getAppNames(config).length === 0) throw new Error('no application was found in config') if (config.debugPort != null && (typeof(config.debugPort) !== 'number' || !config.debugPort)) throw new Error('debugPort in config not a valid number') } extractFile(file, stream = function() {}) { let program = this.get7zipExecutable() let args = ['x', file] if (file.indexOf('.tar.') > 0) { program = 'tar' args = ['xvf', file] } return this.runCommand(program, args, path.dirname(file), stream) } runCommand(command, options = [], folder = null, stream = function() {}) { let baseError = new Error('') return new Promise(function(res, rej) { let fullcommand = path.join(folder ? folder : '', command) if (command.indexOf('/') >= 0 || command.indexOf('\\') >= 0) { fullcommand = command } stream(`[Command] ${fullcommand} ${options.join(' ')}\n`) let processor = spawn(command, options, { shell: true, cwd: folder, }) let timeOuter = setInterval(function() { try { processor.stdin.write('n\n') } catch {} }, 250) processor.stdout.on('data', function(data) { stream(data.toString().replace(/\r\n/g, '\n')) }) processor.stderr.on('data', function(data) { stream(data.toString().replace(/\r\n/g, '\n')) }) processor.stdin.on('error', function() { clearInterval(timeOuter) }) processor.on('error', function(err) { clearInterval(timeOuter) baseError.message = err.message rej(baseError) }) processor.on('exit', function (code) { clearInterval(timeOuter) if (code !== 0) { baseError.message = 'Program returned error code: ' + code return rej(baseError) } res(code) }) }) } runCommandBackground(command, options = [], folder = null, stream = function() {}) { let fullcommand = path.join(folder ? folder : '', command) if (command.indexOf('/') >= 0 || command.indexOf('\\') >= 0) { fullcommand = command } stream(`[Command] ${fullcommand} ${options.join(' ')}\n`) let processor = spawn(command, options, { shell: true, cwd: folder, }) let timeOuter = setInterval(function() { try { processor.stdin.write('n\n') } catch {} }, 250) processor.stdout.on('data', function(data) { stream(data.toString().replace(/\r\n/g, '\n')) }) processor.stderr.on('data', function(data) { stream(data.toString().replace(/\r\n/g, '\n')) }) processor.stdin.on('error', function() { clearInterval(timeOuter) }) processor.on('error', function(err) { clearInterval(timeOuter) }) processor.on('exit', function (code) { clearInterval(timeOuter) }) processor._kill = processor.kill processor.kill = function() { if(os.platform() === 'win32'){ try { execSync('taskkill /pid ' + processor.pid + ' /T /F') } catch {} }else{ processor._kill(); } } return processor } }