mirror of
https://github.com/linuxserver/core.git
synced 2026-02-20 05:07:19 +08:00
249 lines
7.6 KiB
JavaScript
249 lines
7.6 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
// HTTP server for desktop editors
|
|
|
|
// Reads .tern-project files, wraps a Tern server in an HTTP wrapper
|
|
// so that editor plug-ins can talk to it.
|
|
|
|
var tern = require("../lib/tern");
|
|
var fs = require("fs"), path = require("path"), url = require("url");
|
|
var glob = require("glob"), minimatch = require("minimatch");
|
|
|
|
var projectFileName = ".tern-project", portFileName = ".tern-port";
|
|
var maxIdleTime = 6e4 * 5; // Shut down after five minutes of inactivity
|
|
|
|
var persistent = process.argv.indexOf("--persistent") > -1;
|
|
var stripCRs = process.argv.indexOf("--strip-crs") > -1;
|
|
var disableLoadingLocal = process.argv.indexOf("--disable-loading-local") > -1;
|
|
var verbose = process.argv.indexOf("--verbose") > -1;
|
|
var debug = verbose || process.argv.indexOf("--debug") > -1;
|
|
var noPortFile = process.argv.indexOf("--no-port-file") > -1;
|
|
var host = "127.0.0.1";
|
|
var hostArg = process.argv.indexOf("--host");
|
|
if (hostArg > -1) {
|
|
host = process.argv[hostArg + 1];
|
|
if (host == "null" || host == "any") host = null;
|
|
}
|
|
var port = 0;
|
|
var portArg = process.argv.indexOf("--port");
|
|
if (portArg > -1) {
|
|
port = Number(process.argv[portArg + 1]);
|
|
if (isNaN(port)) port = 0;
|
|
}
|
|
|
|
function findProjectDir() {
|
|
var dir = process.cwd();
|
|
for (;;) {
|
|
try {
|
|
if (fs.statSync(path.resolve(dir, projectFileName)).isFile()) return dir;
|
|
} catch(e) {}
|
|
var shorter = path.dirname(dir);
|
|
if (shorter == dir) return null;
|
|
dir = shorter;
|
|
}
|
|
}
|
|
|
|
var defaultConfig = {
|
|
libs: [],
|
|
loadEagerly: false,
|
|
plugins: {},
|
|
ecmaScript: true,
|
|
ecmaVersion: 6,
|
|
dependencyBudget: tern.defaultOptions.dependencyBudget
|
|
};
|
|
var homeDir = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
|
|
if (homeDir && fs.existsSync(path.resolve(homeDir, ".tern-config")))
|
|
defaultConfig = readProjectFile(path.resolve(homeDir, ".tern-config"));
|
|
|
|
function readJSON(fileName) {
|
|
var file = fs.readFileSync(fileName, "utf8");
|
|
try {
|
|
return JSON.parse(file);
|
|
} catch (e) {
|
|
console.error("Bad JSON in " + fileName + ": " + e.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
function readProjectFile(fileName) {
|
|
var data = readJSON(fileName);
|
|
for (var option in defaultConfig) if (!data.hasOwnProperty(option))
|
|
data[option] = defaultConfig[option];
|
|
return data;
|
|
}
|
|
|
|
function findFile(file, projectDir, fallbackDir) {
|
|
var local = path.resolve(projectDir, file);
|
|
if (!disableLoadingLocal && fs.existsSync(local)) return local;
|
|
var shared = path.resolve(fallbackDir, file);
|
|
if (fs.existsSync(shared)) return shared;
|
|
}
|
|
|
|
var distDir = path.resolve(__dirname, "..");
|
|
|
|
function findDefs(projectDir, config) {
|
|
var defs = [], src = config.libs.slice();
|
|
if (config.ecmaScript) {
|
|
if (src.indexOf("ecma6") == -1 && config.ecmaVersion >= 6) src.unshift("ecma6")
|
|
if (src.indexOf("ecma5") == -1) src.unshift("ecma5")
|
|
}
|
|
for (var i = 0; i < src.length; ++i) {
|
|
var file = src[i];
|
|
if (!/\.json$/.test(file)) file = file + ".json";
|
|
var found = findFile(file, projectDir, path.resolve(distDir, "defs"));
|
|
if (!found) {
|
|
try {
|
|
found = require.resolve("tern-" + src[i]);
|
|
} catch (e) {
|
|
process.stderr.write("Failed to find library " + src[i] + ".\n");
|
|
continue;
|
|
}
|
|
}
|
|
if (found) defs.push(readJSON(found));
|
|
}
|
|
return defs;
|
|
}
|
|
|
|
function defaultPlugins(config) {
|
|
var result = ["doc_comment"]
|
|
return result
|
|
}
|
|
|
|
function loadPlugins(projectDir, config) {
|
|
var plugins = config.plugins, options = {};
|
|
for (var plugin in plugins) {
|
|
var val = plugins[plugin];
|
|
if (!val) continue;
|
|
var found = findFile(plugin + ".js", projectDir, path.resolve(distDir, "plugin"));
|
|
if (!found) {
|
|
try {
|
|
found = require.resolve("tern-" + plugin);
|
|
} catch (e) {
|
|
process.stderr.write("Failed to find plugin " + plugin + ".\n");
|
|
continue;
|
|
}
|
|
}
|
|
var mod = require(found);
|
|
if (mod.hasOwnProperty("initialize")) mod.initialize(distDir);
|
|
options[path.basename(plugin)] = val;
|
|
}
|
|
|
|
defaultPlugins(config).forEach(function(name) {
|
|
if (!plugins.hasOwnProperty(name)) {
|
|
options[name] = true;
|
|
require("../plugin/" + name);
|
|
}
|
|
});
|
|
return options;
|
|
}
|
|
|
|
var projectDir = findProjectDir();
|
|
if (projectDir) {
|
|
var config = readProjectFile(path.resolve(projectDir, projectFileName));
|
|
} else {
|
|
projectDir = process.cwd();
|
|
var config = defaultConfig;
|
|
}
|
|
|
|
var httpServer = require("http").createServer(function(req, resp) {
|
|
clearTimeout(shutdown);
|
|
shutdown = setTimeout(doShutdown, maxIdleTime);
|
|
|
|
var target = url.parse(req.url, true);
|
|
if (target.pathname == "/ping") return respondSimple(resp, 200, "pong");
|
|
if (target.pathname != "/") return respondSimple(resp, 404, "No service at " + target.pathname);
|
|
|
|
if (req.method == "POST") {
|
|
var body = "";
|
|
req.on("data", function (data) { body += data; });
|
|
req.on("end", function() { respond(resp, body); });
|
|
} else if (req.method == "GET") {
|
|
if (target.query.doc) respond(resp, target.query.doc);
|
|
else respondSimple(resp, 400, "Missing query document");
|
|
}
|
|
});
|
|
|
|
var server = startServer(projectDir, config, httpServer);
|
|
|
|
function startServer(dir, config, httpServer) {
|
|
var defs = findDefs(dir, config);
|
|
var plugins = loadPlugins(dir, config);
|
|
var server = new tern.Server({
|
|
getFile: function(name, c) {
|
|
if (config.dontLoad && config.dontLoad.some(function(pat) {return minimatch(name, pat)}))
|
|
c(null, "");
|
|
else
|
|
fs.readFile(path.resolve(dir, name), "utf8", c);
|
|
},
|
|
normalizeFilename: function(name) {
|
|
var pt = path.resolve(dir, name)
|
|
try { pt = fs.realPathSync(path.resolve(dir, name), true) }
|
|
catch(e) {}
|
|
return path.relative(dir, pt)
|
|
},
|
|
async: true,
|
|
defs: defs,
|
|
plugins: plugins,
|
|
debug: debug,
|
|
projectDir: dir,
|
|
ecmaVersion: config.ecmaVersion,
|
|
dependencyBudget: config.dependencyBudget,
|
|
stripCRs: stripCRs,
|
|
parent: {httpServer: httpServer}
|
|
});
|
|
|
|
if (config.loadEagerly) config.loadEagerly.forEach(function(pat) {
|
|
glob.sync(pat, { cwd: dir }).forEach(function(file) {
|
|
server.addFile(file);
|
|
});
|
|
});
|
|
return server;
|
|
}
|
|
|
|
function doShutdown() {
|
|
if (persistent) return;
|
|
console.log("Was idle for " + Math.floor(maxIdleTime / 6e4) + " minutes. Shutting down.");
|
|
process.exit();
|
|
}
|
|
|
|
var shutdown = setTimeout(doShutdown, maxIdleTime);
|
|
|
|
process.stdin.on("end", function() { process.exit(); });
|
|
process.stdin.resume();
|
|
|
|
httpServer.listen(port, host, function() {
|
|
var port = httpServer.address().port;
|
|
if (!noPortFile) {
|
|
var portFile = path.resolve(projectDir, portFileName);
|
|
fs.writeFileSync(portFile, String(port), "utf8");
|
|
process.on("exit", function() {
|
|
try {
|
|
var cur = Number(fs.readFileSync(portFile, "utf8"));
|
|
if (cur == port) fs.unlinkSync(portFile);
|
|
} catch(e) {}
|
|
});
|
|
}
|
|
process.on("SIGINT", function() { process.exit(); });
|
|
process.on("SIGTERM", function() { process.exit(); });
|
|
console.log("Listening on port " + port);
|
|
});
|
|
|
|
function respondSimple(resp, status, text) {
|
|
resp.writeHead(status, {"content-type": "text/plain; charset=utf-8"});
|
|
resp.end(text);
|
|
if (verbose) console.log("Response: " + status + " " + text);
|
|
}
|
|
|
|
function respond(resp, doc) {
|
|
try { var doc = JSON.parse(doc); }
|
|
catch(e) { return respondSimple(resp, 400, "JSON parse error: " + e.message); }
|
|
if (verbose) console.log("Request: " + JSON.stringify(doc, null, 2));
|
|
|
|
server.request(doc, function(err, data) {
|
|
if (err) return respondSimple(resp, 400, String(err));
|
|
resp.writeHead(200, {"content-type": "application/json; charset=utf-8"});
|
|
if (verbose) console.log("Response: " + JSON.stringify(data, null, 2) + "\n");
|
|
resp.end(JSON.stringify(data));
|
|
});
|
|
}
|