total5
Version:
Total.js framework v5
554 lines (450 loc) • 13.4 kB
JavaScript
// Debug module (Watcher)
// The MIT License
// Copyright 2012-2024 (c) Peter Širka <petersirka@gmail.com>
;
require('./index');
var Meta = {
isWatcher: process.connected !== true,
callback: null, // watcher callback
delay: null
};
var first = process.argv.indexOf('--restart') === -1;
var options = null;
module.exports = function(opt) {
options = opt || {};
// options.ip = '127.0.0.1';
// options.port = parseInt(process.argv[2]);
// options.unixsocket = require('node:path').join(require('node:os').tmpdir(), 'app_name');
// 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;
// options.cluster = 'auto' || or NUMBER
// options.limit = 10;
// options.timeout = 5000;
// options.edit = 'wss://.....com/?id=myprojectname'
};
module.exports.watcher = function(callback) {
Meta.delay && clearTimeout(Meta.delay);
Meta.delay = null;
Meta.callback = callback;
runwatching();
};
function killapp(pid) {
try {
process.kill(pid, 'SIGINT');
} catch {}
}
function runapp() {
!options && (options = {});
global.DEBUG = options.release !== true;
if (options.servicemode) {
var types = options.servicemode === true || options.servicemode === 1 ? '' : options.servicemode.split(',').trim();
F.load(types);
} else
F.http(options);
}
function runwatching() {
if (!options)
options = {};
var directory = process.cwd();
var root = directory;
var LIVERELOADCHANGE = '';
const FILENAME = F.TUtils.getName(process.argv[1] || 'index.js');
const VERSION = F.version_header;
const REG_FILES = /(config|version|bundles\.debug|\.js|\.ts|\.flow|\.py|\.resource)+$/i;
const REG_PUBLIC = /\/public\//i;
const REG_INDEX = new RegExp(FILENAME.replace(/\.js$/, '') + '_.*?\\.js$');
const REG_EXTENSION = /\.(js|ts|py|resource|package|bundle|build|flow|url|html)$/i;
const REG_RELOAD = /\.(js|ts|py|css|html|htm|jpg|png|gif|ico|svg|webp|resource)$/i;
const isRELOAD = !!options.livereload;
const SPEED = isRELOAD ? 1000 : 1500;
const ARGV = F.TUtils.clone(process.argv);
const PIDNAME = FILENAME.replace(/\.(js|ts)$/, '.pid');
if (isRELOAD && typeof(options.livereload) === 'string')
options.livereload = options.livereload.replace(/^(https|http):\/\//g, '');
function copyFile(oldname, newname, callback) {
var writer = F.Fs.createWriteStream(newname);
callback && writer.on('finish', callback);
F.Fs.createReadStream(oldname).pipe(writer);
}
function app() {
if (!Meta.callback) {
global.OBSOLETE = NOOP;
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
}
var skipbundle = false;
F.directory = directory;
try {
if (F.Fs.readFileSync(F.path.join(directory, 'bundles.debug'))) {
skipbundle = true;
F.directory = directory = F.path.join(directory, '.src');
}
} catch(e) {}
const fork = F.Child.fork;
const directories = [
F.Path.join(directory, 'controllers'),
F.Path.join(directory, 'definitions'),
F.Path.join(directory, 'extensions'),
F.Path.join(directory, 'modules'),
F.Path.join(directory, 'models'),
F.Path.join(directory, 'schemas'),
F.Path.join(directory, 'actions'),
F.Path.join(directory, 'resources'),
F.Path.join(directory, 'source'),
F.Path.join(directory, 'workers'),
F.Path.join(directory, 'middleware'),
F.Path.join(directory, 'bundles'),
F.Path.join(directory, 'flowstreams'),
F.Path.join(directory, 'transforms'),
F.Path.join(directory, 'startup'),
F.Path.join(directory, 'plugins')
];
const SRC = F.Path.join(directory, '.src');
const prefix = '--------> ';
if (options.watch) {
for (let item of options.watch) {
if (item[0] === '/')
item = item.substring(1);
if (item[item.length - 1] === '/')
item = item.substring(0, item.length - 1);
directories.push(F.Path.join(directory, item));
}
}
var WS = null;
var files = {};
var force = false;
var changes = [];
var app = null;
var status = Meta.callback ? 1 : 0;
var pid = '';
var isloaded = false;
var skiprestart = false;
var isbundle = false;
var ignore = {};
var counter = 0;
var speed = isRELOAD ? 1000 : 4000;
ignore['/' + PIDNAME] = 1;
ignore['/debug.pid'] = 1;
ignore['/debug.js'] = 1;
ignore['/bundle.json'] = 1;
ignore['/package.json'] = 1;
ignore['/readme.md'] = 1;
if (isRELOAD && !Meta.callback) {
if (typeof(options.livereload) === 'string') {
WEBSOCKETCLIENT(function(client) {
client.options.type = 'text';
client.on('open', () => WS = client);
client.on('close', () => WS = null);
client.connect('wss://livereload.totaljs.com/?hostname=' + encodeURIComponent(options.livereload));
});
} else {
var tmppath = F.Path.join(F.Os.tmpdir(), 'total5livereload');
F.Fs.mkdir(tmppath, function() {
F.console = NOOP;
F.route('SOCKET / @text', function($) {
$.autodestroy(() => WS = null);
WS = self;
});
var port = typeof(options.livereload) === 'number' ? options.livereload : 35729;
F.directory = tmppath;
F.http({ port: port });
console.log('> Live reload: ws://127.0.0.1:' + port);
});
}
}
if (!skipbundle) {
try {
F.Fs.statSync(F.path.root('bundles'));
isbundle = true;
} catch(e) {}
}
if (isbundle || isRELOAD) {
directories.push(F.Path.join(directory, 'public'));
directories.push(F.Path.join(directory, 'views'));
}
function onFilter(path, isdir) {
var p = path.substring(directory.length);
if (isbundle)
return isdir ? SRC !== path : !ignore[p];
if (isRELOAD)
return isdir ? true : REG_RELOAD.test(path);
if (isdir)
return true;
if (!REG_PUBLIC.test(path) && REG_EXTENSION.test(path))
return true;
return false;
}
function skiproot(filename) {
if (filename.substring(filename.length - 3) === '.js') {
for (var i = 0; i < filename.length - 3; i++) {
if (filename[i] === '/' || filename[i] === '\\')
return;
}
return true;
}
}
function onComplete(f) {
F.Fs.readdir(directory, function(err, arr) {
var length = arr.length;
for (var i = 0; i < length; i++) {
var name = arr[i];
if (name !== FILENAME && !REG_INDEX.test(name) && REG_FILES.test(name) && !skiproot(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(filename) {
isRELOAD && setTimeout2('livereload', (filename) => WS && WS.send(filename || 'unknown'), 500, null, filename);
}
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 === F.config.directory_themes) {
index = fn.indexOf('/', index + 1);
dir = fn.substring(index, fn.indexOf('/', index + 1) + 1);
}
return dir === '/views/' || dir === '/public/' ? fn : '';
}
function makestamp() {
return '--- # --- [ ' + new Date().format('yyyy-MM-dd HH:mm:ss') + ' ] ';
}
function refresh() {
var reload = false;
LIVERELOADCHANGE = '';
Object.keys(files).wait(function(filename, next) {
F.Fs.stat(filename, function(err, stat) {
var stamp = makestamp();
if (err) {
delete files[filename];
var tmp = isViewPublic(filename);
LIVERELOADCHANGE = normalize(filename.replace(directory, ''));
var log = stamp.replace('#', 'REM') + prefix + LIVERELOADCHANGE;
if (tmp) {
if (isbundle) {
F.Fs.unlinkSync(F.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;
reload = true;
next();
return;
}
if (options.threads && filename.substring(directory.length).indexOf('/threads/') !== -1 && files[filename]) {
files[filename] = ticks;
next();
return;
}
LIVERELOADCHANGE = normalize(filename.replace(directory, ''));
var log = stamp.replace('#', files[filename] === 0 ? 'ADD' : 'UPD') + prefix + LIVERELOADCHANGE;
if (files[filename]) {
var tmp = isViewPublic(filename);
if (tmp) {
var skip = true;
if (isbundle) {
if (filename.lastIndexOf('--') === -1)
copyFile(filename, F.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) {
// Due to bundes/*.url
if (status === 2) {
status = 1;
force = false;
changes.length = 0;
}
reload && livereload(LIVERELOADCHANGE);
if (counter % 150 === 0)
speed = isRELOAD ? 3000 : (counter % 750 === 0 ? 30000 : 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.length = 0;
force = false;
}, 3);
}
function refresh_directory() {
counter++;
F.TUtils.ls(directories, onComplete, onFilter);
}
function restart() {
if (Meta.callback) {
if (first)
first = false;
else
Meta.callback(changes);
return;
}
if (app !== null) {
try
{
skiprestart = true;
killapp(app.pid);
if (options.inspector) {
setTimeout(restart, 1000);
return;
}
} catch {}
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');
port && arr.push(port);
app = fork(F.Path.join(root, 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 = 2;
livereload(LIVERELOADCHANGE);
break;
}
});
app.on('exit', function() {
// checks unexpected exit
if (skiprestart === false) {
app = null;
process.exit(1);
return;
}
skiprestart = false;
if (status === 255)
app = null;
});
F.emit('$watcher', app);
}
process.on('SIGTERM', end);
process.on('SIGINT', end);
process.on('exit', end);
function end() {
if (process.isending)
return;
process.isending = true;
F.Fs.unlink(pid, noop);
if (app === null) {
process.exit(0);
return;
}
skiprestart = true;
killapp(app.pid);
app = null;
process.exit(0);
}
function noop() {}
if (process.pid > 0) {
!Meta.callback && console.log(prefix.substring(8) + 'Total.js watcher PID: ' + process.pid + ' (v' + VERSION + ')');
pid = F.Path.join(directory, PIDNAME);
F.Fs.writeFileSync(pid, process.pid + '');
setInterval(function() {
F.Fs.stat(pid, function(err) {
if (err) {
F.Fs.unlink(pid, noop);
if (app !== null) {
skiprestart = true;
killapp(app.pid);
}
process.exit(0);
}
});
}, 4000);
}
restart();
refresh_directory();
F.restart = restart;
}
var filename = F.Path.join(process.cwd(), PIDNAME);
if (F.Fs.existsSync(filename)) {
F.Fs.unlinkSync(filename);
setTimeout(app, 3500);
} else
app();
}
function normalize(path) {
return F.isWindows ? path.replace(/\\/g, '/') : path;
}
function init() {
if (options.cluster) {
options.count = options.cluster;
F.TCluster.http(options);
return;
}
process.on('uncaughtException', e => e.toString().indexOf('ESRCH') == -1 && console.log(e));
process.title = 'total: debug';
if (Meta.isWatcher) {
if (options.edit) {
require('./edit').init(options.edit.replace(/^http/, 'ws'));
setTimeout(runwatching, 1000);
} else
setImmediate(runwatching);
} else
setImmediate(runapp);
}
Meta.delay = setTimeout(init, 100);