total.js
Version:
MVC framework for Node.js
502 lines (414 loc) • 12.7 kB
JavaScript
// Copyright 2012-2020 (c) Peter Širka <petersirka@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
/**
* @module FrameworkDebug
* @version 3.4.3
*/
const Path = require('path');
const Fs = require('fs');
const debugging = process.argv.indexOf('debugging') !== -1;
const Os = require('os');
const isWindows = Os.platform().substring(0, 3).toLowerCase() === 'win';
var first = process.argv.indexOf('restart') === -1;
var options = null;
var initdelay;
var watchercallback;
module.exports = function(opt) {
options = opt;
// options.ip = '127.0.0.1';
// options.port = parseInt(process.argv[2]);
// options.config = { name: 'Total.js' };
// options.https = { key: Fs.readFileSync('keys/agent2-key.pem'), cert: Fs.readFileSync('keys/agent2-cert.pem')};
// options.sleep = 3000;
// options.inspector = 9229;
// options.debugger = 40894;
// options.watch = ['adminer'];
// options.livereload = true;
};
module.exports.watcher = function(callback) {
initdelay && clearTimeout(initdelay);
initdelay = null;
watchercallback = callback;
runwatching();
};
function runapp() {
!options && (options = {});
require('total.js');
var port = parseInt(process.argv[process.argv.length - 1]);
if (!isNaN(port))
options.port = port;
if (port > 0 && !options.port)
options.port = port || 8000;
if (options.https)
F.https('debug', options);
else
F.http('debug', options);
if (first)
EMIT('debug-start');
else
EMIT('debug-restart');
}
function runwatching() {
!options && (options = {});
require('./index');
const FILENAME = U.getName(process.argv[1] || 'debug.js');
const directory = process.cwd();
const VERSION = F.version_header;
const REG_CONFIGS = /configs\//g;
const REG_FILES = /config-debug|config-release|config|versions|workflows|sitemap|dependencies|\.js$|\.resource$/i;
const REG_THEMES = /\/themes\//i;
const REG_PUBLIC = /\/public\//i;
const REG_COMPONENTS = /components\/.*?\.html|\.package\/.*?$/i;
const REG_THEMES_INDEX = /themes(\/|\\)?[a-z0-9_.-]+(\/|\\)?index\.js$/i;
const REG_EXTENSION = /\.(js|resource|package|bundle)$/i;
const REG_RELOAD = /\.(js|css|html|htm|jpg|png|gif|ico|svg|resource)$/i;
const isRELOAD = !!options.livereload;
const SPEED = isRELOAD ? 1000 : 1500;
const ARGV = CLONE(process.argv);
const PIDNAME = FILENAME.replace(/\.js$/, '.pid');
function copyFile(oldname, newname, callback) {
var writer = Fs.createWriteStream(newname);
callback && writer.on('finish', callback);
Fs.createReadStream(oldname).pipe(writer);
}
function app() {
if (!watchercallback) {
global.OBSOLETE = NOOP;
F.config.allow_ssc_validation = true;
F.$configure_configs();
}
F.directory = directory;
const fork = require('child_process').fork;
const directories = [
U.combine(CONF.directory_components),
U.combine(CONF.directory_controllers),
U.combine(CONF.directory_definitions),
U.combine(CONF.directory_operations),
U.combine(CONF.directory_isomorphic),
U.combine(CONF.directory_modules),
U.combine(CONF.directory_models),
U.combine(CONF.directory_schemas),
U.combine(CONF.directory_tasks),
U.combine(CONF.directory_resources),
U.combine(CONF.directory_source),
U.combine(CONF.directory_workers),
U.combine(CONF.directory_packages),
U.combine(CONF.directory_themes),
U.combine(CONF.directory_configs),
U.combine(CONF.directory_bundles),
U.combine('/startup/'),
U.combine('/plugins/')
];
if (global.THREAD)
directories.push(U.combine('/threads/' + global.THREAD + '/'));
const SRC = U.combine(CONF.directory_src);
const prefix = '--------> ';
options.watch && options.watch.forEach(function(item) {
if (item[0] === '/')
item = item.substring(1);
if (item[item.length - 1] === '/')
item = item.substring(0, item.length - 1);
directories.push(U.combine(item));
});
var files = {};
var force = false;
var changes = [];
var app = null;
var status = watchercallback ? 1 : 0;
var pid = '';
var isLoaded = false;
var isSkip = false;
var isBUNDLE = false;
var blacklist = {};
var counter = 0;
var WS = null;
var speed = isRELOAD ? 1000 : 4000;
blacklist['/' + PIDNAME] = 1;
blacklist['/debug.pid'] = 1;
blacklist['/debug.js'] = 1;
blacklist['/bundle.json'] = 1;
blacklist['/package.json'] = 1;
blacklist['/readme.md'] = 1;
if (isRELOAD && !watchercallback) {
var tmppath = Path.join(Os.tmpdir(), 'totaljslivereload');
Fs.mkdir(tmppath, function() {
F.console = NOOP;
F.websocket('/', function() {
var self = this;
self.autodestroy(function() {
WS = null;
});
WS = self;
});
F.http('release', { port: typeof(options.livereload) === 'number' ? options.livereload : 35729, directory: tmppath });
});
}
try {
Fs.statSync(F.path.root(CONF.directory_bundles));
isBUNDLE = true;
} catch(e) {}
if (isBUNDLE || isRELOAD) {
directories.push(U.combine(CONF.directory_public));
directories.push(U.combine(CONF.directory_views));
}
function onFilter(path, isDirectory) {
if (isBUNDLE)
return isDirectory ? SRC !== path : !blacklist[path.substring(directory.length)];
if (isRELOAD)
return isDirectory ? true : REG_RELOAD.test(path);
return isDirectory && REG_THEMES.test(path) ? true : isDirectory ? true : !REG_PUBLIC.test(path) && (REG_EXTENSION.test(path) || REG_COMPONENTS.test(path) || REG_CONFIGS.test(path) || REG_THEMES_INDEX.test(path));
}
function onComplete(f) {
Fs.readdir(directory, function(err, arr) {
var length = arr.length;
for (var i = 0; i < length; i++) {
var name = arr[i];
name !== FILENAME && REG_FILES.test(name) && f.push(name);
}
length = f.length;
for (var i = 0; i < length; i++) {
var name = f[i];
if (files[name] === undefined)
files[name] = isLoaded ? 0 : null;
}
refresh();
});
}
function livereload() {
isRELOAD && setTimeout2('livereload', () => WS && WS.send('reload'), 500);
}
function isViewPublic(filename) {
if (!isBUNDLE && !isRELOAD)
return false;
var fn = filename.substring(directory.length);
var index = fn.indexOf('/', 1);
var dir = fn.substring(0, index + 1);
if (dir === CONF.directory_themes) {
index = fn.indexOf('/', index + 1);
dir = fn.substring(index, fn.indexOf('/', index + 1) + 1);
}
return CONF.directory_views === dir || CONF.directory_public === dir ? fn : '';
}
function makestamp() {
return '--- # --- [ ' + new Date().format('yyyy-MM-dd HH:mm:ss') + ' ] ';
}
function refresh() {
var reload = false;
Object.keys(files).wait(function(filename, next) {
Fs.stat(filename, function(err, stat) {
var stamp = makestamp();
if (err) {
delete files[filename];
var tmp = isViewPublic(filename);
var log = stamp.replace('#', 'REM') + prefix + normalize(filename.replace(directory, ''));
if (tmp) {
if (isBUNDLE) {
Fs.unlinkSync(Path.join(SRC, tmp));
console.log(log);
}
reload = true;
} else {
changes.push(log);
force = true;
}
} else {
var ticks = stat.mtime.getTime();
if (files[filename] != null && files[filename] !== ticks) {
if (filename.endsWith('.bundle') && files[filename.replace(/\.bundle$/, '.url')]) {
// Bundle from URL address
files[filename] = ticks;
next();
return;
}
var log = stamp.replace('#', files[filename] === 0 ? 'ADD' : 'UPD') + prefix + normalize(filename.replace(directory, ''));
if (files[filename]) {
var tmp = isViewPublic(filename);
if (tmp) {
var skip = true;
if (isBUNDLE) {
if (filename.lastIndexOf('--') === -1)
copyFile(filename, Path.join(SRC, tmp));
else
skip = false;
}
if (skip) {
files[filename] = ticks;
reload = true;
next();
return;
}
}
}
changes.push(log);
force = true;
}
files[filename] = ticks;
}
next();
});
}, function() {
isLoaded = true;
if (status !== 1 || !force) {
reload && livereload();
if (counter % 150 === 0)
speed = isRELOAD ? 3000 : 6000;
setTimeout(refresh_directory, speed);
return;
}
restart();
counter = 0;
speed = SPEED;
setTimeout(refresh_directory, speed);
var length = changes.length;
for (var i = 0; i < length; i++)
console.log(changes[i]);
changes = [];
force = false;
}, 3);
}
function refresh_directory() {
counter++;
U.ls(directories, onComplete, onFilter);
}
function restart() {
if (watchercallback) {
if (first)
first = false;
else
watchercallback(changes);
return;
}
if (app !== null) {
try
{
isSkip = true;
process.kill(app.pid);
if (options.inspector) {
setTimeout(restart, 1000);
return;
}
} catch (err) {}
app = null;
}
var arr = ARGV.slice(2);
var port = arr.pop();
if (process.execArgv.indexOf('--debug') !== -1 || options.debugger) {
var key = '--debug=' + (options.debugger || 40894);
process.execArgv.indexOf(key) === -1 && process.execArgv.push(key);
}
if (process.execArgv.indexOf('--inspect') !== -1 || options.inspector) {
var key = '--inspect=' + (options.inspector || 9229);
process.execArgv.indexOf(key) === -1 && process.execArgv.push(key);
}
if (first)
first = false;
else
arr.push('restart');
arr.push('debugging');
port && arr.push(port);
app = fork(Path.join(directory, FILENAME), arr);
app.on('message', function(msg) {
switch (msg) {
case 'total:eaddrinuse':
process.exit(1);
break;
case 'total:restart':
console.log(makestamp().replace('#', 'RES'));
restart();
break;
case 'total:ready':
if (status === 0) {
app.send('total:debug');
status = 1;
}
livereload();
break;
}
});
app.on('exit', function() {
// checks unexpected exit
if (isSkip === false) {
app = null;
process.exit();
return;
}
isSkip = false;
if (status === 255)
app = null;
});
}
process.on('SIGTERM', end);
process.on('SIGINT', end);
process.on('exit', end);
function end() {
if (process.isending)
return;
process.isending = true;
Fs.unlink(pid, noop);
if (app === null) {
process.exit(0);
return;
}
isSkip = true;
process.kill(app.pid);
app = null;
process.exit(0);
}
function noop() {}
if (process.pid > 0) {
!watchercallback && console.log(prefix.substring(8) + 'DEBUG PID: ' + process.pid + ' (v' + VERSION + ')');
pid = Path.join(directory, PIDNAME);
Fs.writeFileSync(pid, process.pid + '');
setInterval(function() {
Fs.stat(pid, function(err) {
if (err) {
Fs.unlink(pid, noop);
if (app !== null) {
isSkip = true;
process.kill(app.pid);
}
process.exit(0);
}
});
}, 4000);
}
restart();
refresh_directory();
}
var filename = Path.join(directory, PIDNAME);
if (Fs.existsSync(filename)) {
Fs.unlinkSync(filename);
setTimeout(app, 3500);
} else
app();
}
function normalize(path) {
return isWindows ? path.replace(/\\/g, '/') : path;
}
function init() {
process.on('uncaughtException', e => e.toString().indexOf('ESRCH') == -1 && console.log(e));
process.title = 'total: debug';
if (debugging)
setImmediate(runapp);
else
setImmediate(runwatching);
}
initdelay = setTimeout(init, 100);