UNPKG

jsx

Version:

a faster, safer, easier JavaScript

455 lines (378 loc) 13.3 kB
/*** The entry point module of JSX compiler on NodeJS */ /* * Copyright (c) 2012 DeNA Co., Ltd. * * 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. */ import "js.jsx"; import "js/nodejs.jsx"; import "console.jsx"; import "timer.jsx"; import "./util.jsx"; import "./emitter.jsx"; import "./jsemitter.jsx"; import "./platform.jsx"; import "./jsx-command.jsx"; import "./meta.jsx"; /** * Access to platform via NodeJS API */ class NodePlatform extends Platform { var _root : string; var _cwd = Util.resolvePath(process.cwd()); function constructor () { this(node.path.dirname(node.__dirname)); } function constructor (root : string) { this._root = Util.resolvePath(root); } override function getRoot () : string { return this._root; } function _absPath(path : string) : string { return (path.charAt(0) == "/" || path.match(/^[a-zA-Z]:\//)) ? path // path is already absolute : this._cwd + "/" + path; } override function fileExists (name : string) : boolean { name = Util.resolvePath(name); if (this.fileContent.hasOwnProperty(name)) { return true; } return node.fs.existsSync(this._absPath(name)); } override function getFilesInDirectory (path : string) : string[] { return node.fs.readdirSync(this._absPath(path)); } override function load (name : string) : string { name = Util.resolvePath(name); if (this.fileContent.hasOwnProperty(name)) { return this.fileContent[name]; } else if (name == "-") { var fd = process.stdin.fd; var content = ""; var BUFFER_SIZE = 4096; var buffer = new Buffer(BUFFER_SIZE); var n; while( (n = node.fs.readSync(fd, buffer, 0, BUFFER_SIZE)) > 0) { content += buffer.slice(0, n).toString(); } return content; } else { var content = node.fs.readFileSync(this._absPath(name), "utf-8"); // cache the content without condition because the platform object // is created for each compilation for now. this.fileContent[name] = content; return content; } } override function save (outputFile : Nullable.<string>, content : string) : void { if (outputFile == null) { process.stdout.write(content); } else { outputFile = this._absPath(outputFile); this.mkpath(Util.dirname(outputFile)); node.fs.writeFileSync(outputFile, content); } } override function setWorkingDir (dir : string) : void { this._cwd = this._absPath(dir); } override function mkpath (path : string) : void { path = this._absPath(path); if (! node.fs.existsSync(path)) { var dirOfPath = Util.dirname(path); if (dirOfPath != path) { this.mkpath(dirOfPath); } node.fs.mkdirSync(path); } } override function makeFileExecutable(file : string, runEnv : string) : void { if (runEnv == "node") { var filePath = this._absPath(file); var contents = node.fs.readFileSync(filePath, "utf-8"); contents = "#!/usr/bin/env node\n" + contents; node.fs.writeFileSync(filePath, contents); node.fs.chmodSync(this._absPath(file), "0755"); } } override function execute(scriptFile : Nullable.<string>, jsSource : string, args : string[]) : void { // to refer module_modules, the temporary dirctory must be in the project directory var jsFile = this._absPath(Util.format(".jsx.%1.%2.%3.js", [ node.path.basename(scriptFile ?: "-"), process.pid.toString(), Date.now().toString(16) ])); node.fs.writeFileSync(jsFile, jsSource); process.on("exit", function(stream) { node.fs.unlinkSync(jsFile); }); if (process.env["JSX_RUNJS"]) { var child = node.child_process.spawn(process.env["JSX_RUNJS"], [jsFile].concat(args)); child.stdin.end(); child.stdout.on("data", function (data) { process.stdout.write(data as string); }); child.stderr.on("data", function (data) { process.stderr.write(data as string); }); } else { process.argv = [process.argv[0], jsFile].concat(args); node.require(jsFile); // evaluates it in this process } } static function getEnvOpts () : string[] { var opts = process.env["JSX_OPTS"]; if (! opts) return new string[]; return opts.split(/\s+/); } override function runCompilationServer(arg : variant) : number { var port = arg as int; return CompilationServer.start(this, port); } } /** * NodePlatform variation for compiler server */ class CompilationServerPlatform extends NodePlatform { // response contents var _stdout = ""; var _stderr = ""; var _file = new Map.<string>; var _executableFile = new Map.<string>; var _statusCode = 1; // 0=success, other=failure var _scriptFile = null : Nullable.<string>; // file name on --run var _scriptSource = null : Nullable.<string>; // source code on --run var _scriptArgs = null : Nullable.<string[]>; // script args on --run // request parameters var _requestId = 0; var _request = null : ServerRequest; var _response = null : ServerResponse; function constructor(root : string, reqId : number, req : ServerRequest, res : ServerResponse) { super(root); this._requestId = reqId; this._request = req; this._response = res; } override function save (outputFile : Nullable.<string>, content : string) : void { if (outputFile == null) { this._stdout += content; } else { this._file[outputFile] = content; } } override function log(message : string) : void { this._stdout += message + "\n"; } override function warn(message : string) : void { this._stderr += message + "\n"; } override function error(message : string) : void { this._stderr += message + "\n"; } // does not execute it, just sets script contents instead override function execute(jsFile : Nullable.<string>, jsSource : string, jsArgs : string[]) : void { this._scriptFile = jsFile; this._scriptSource = jsSource; this._scriptArgs = jsArgs; } function setStatusCode(statusCode : number) : void { this._statusCode = statusCode; } function getContents() : Map.<variant> { var content = { stdout : this._stdout, stderr : this._stderr, file : this._file, executableFile : this._executableFile, statusCode : this._statusCode } : Map.<variant>; if (this._scriptSource != null) { content["run"] = { scriptFile : this._scriptFile, scriptSource : this._scriptSource, scriptArgs : this._scriptArgs } : variant; } return content; } override function makeFileExecutable(file : string, runEnv : string) : void { this._executableFile[file] = runEnv; } override function runCompilationServer(arg : variant) : number { this.error('--compilation-server is not supported'); return 1; } } class CompilationServer { var _requestSequence = 0; // do not shutdown automatically static const AUTO_SHUTDOWN = ! process.env["JSX_NO_AUTO_SHUTDOWN"]; // how long the server lives after the last requst static const LIFE = 10 * 60 * 1000; // for server status var _home : string; var _pidFile : string; var _portFile : string; var _platform = null : Platform; var _httpd = null : HTTPServer; var _timer = null : TimerHandle; function constructor(parentPlatform : Platform) { this._platform = parentPlatform; this._home = process.env["JSX_HOME"] ?: (process.env["HOME"] ?: process.env["USERPROFILE"]) + "/.jsx"; if (! parentPlatform.fileExists(this._home)) { parentPlatform.mkpath(this._home); } this._pidFile = this._home + "/pid"; this._portFile = this._home + "/port"; } static function start(platform : Platform, port : int) : number { var server = new CompilationServer(platform); server._httpd = node.http.createServer((request, response) -> { server.handleRequest(request, response); }); server._httpd.listen(port); platform.save(server._pidFile, process.pid as string); platform.save(server._portFile, port as string); console.info("%s [%s] listen http://localhost:%s/", (new Date).toISOString(), process.pid, port); server._timer = Timer.setTimeout(() -> { server.shutdown("timeout"); }, CompilationServer.LIFE); process.on("SIGTERM", () -> { server.shutdown("SIGTERM"); }); process.on("SIGINT", () -> { server.shutdown("SIGINT"); }); // shutdown if the compiler has been updated if (CompilationServer.AUTO_SHUTDOWN) { node.fs.watch(node.__filename, { persistent: false } : Map.<variant>, (event, filename) -> { server.shutdown(event); }); } return 0; } function shutdown(reason : string) : void { try { node.fs.unlinkSync(this._portFile); } catch (e : Error) { } try { node.fs.unlinkSync(this._pidFile); } catch (e : Error) { } Timer.clearTimeout(this._timer); this._httpd.close(); console.info("%s [%s] shutdown by %s, handled %s requests", (new Date).toISOString(), process.pid, reason, this._requestSequence); } function handleRequest(request : ServerRequest, response : ServerResponse) : void { var startTime = new Date(); var id = ++this._requestSequence; Timer.clearTimeout(this._timer); // reset this._timer = Timer.setTimeout(() -> { this.shutdown("timeout"); }, CompilationServer.LIFE); if (request.method == "GET") { console.info("%s #%s start %s", startTime.toISOString(), id, request.url); this.handleGET(id, startTime, request, response); return; } // POST: handle compilation request var c = new CompilationServerPlatform(this._platform.getRoot(), id, request, response); var matched = request.url.match(/\?(.*)/); if (!(matched && matched[1])) { c.error("invalid request to compilation server"); this.finishRequest(id, startTime, response, 400, c.getContents()); return; } var query = String.decodeURIComponent(matched[1]); console.info("%s #%s start %s", startTime.toISOString(), id, query.replace(/\n/g, "\\n")); var inputData = ""; request.on("data", function (chunk) { inputData += chunk as string; }); request.on("end", function () { if (inputData) { c.setFileContent("-", inputData); // as stdin } try { var args = JSON.parse(query) as string[]; c.setStatusCode(JSXCommand.main(c, args)); } catch (e : Error) { console.error("%s #%s %s", startTime.toISOString(), id, e.stack); c.error(e.stack); } this.finishRequest(id, startTime, response, 200, c.getContents()); }); request.on("close", function () { c.error("the connecion is unexpectedly closed.\n"); this.finishRequest(id, startTime, response, 500, c.getContents()); }); } function handleGET(id : number, startTime : Date, request : ServerRequest, response : ServerResponse) : void { // show compiler information this.finishRequest(id, startTime, response, 200, { version_string : Meta.VERSION_STRING, version_number : Meta.VERSION_NUMBER, last_commit_hash : Meta.LAST_COMMIT_HASH, last_commit_date : Meta.LAST_COMMIT_DATE, status : true } : variant); } function finishRequest (id : number, startTime : Date, response : ServerResponse, statusCode : number, data : variant) : void { var content = JSON.stringify(data); var headers = { "Content-Type" : "application/json", "Content-Length" : Buffer.byteLength(content, "utf-8") as string, "Cache-Control" : "no-cache" }; response.writeHead(statusCode, headers); response.end(content, "utf-8"); var now = new Date(); var elapsed = now.getTime() - startTime.getTime(); console.info("%s #%s finish, elapsed %s [ms]", now.toISOString(), id, elapsed); } } class _Main { static function main (args : string[]) : void { var exitCode = JSXCommand.main(new NodePlatform(), NodePlatform.getEnvOpts().concat(args)); // NOTE: // nodejs 0.8.0 on Windows doesn't flush stdout buffer before exitting. // use "drain" event for workaround // https://groups.google.com/forum/#!msg/nodejs/qXkr1C2c8vs/567P_mVZacsJ if (exitCode == 0) { return; } var stdoutIsFlushed = process.stdout.write(""); var stderrIsFlushed = process.stderr.write(""); var exitIfFlushed = function (data : variant) : void { if (stdoutIsFlushed && stderrIsFlushed) { process.exit(exitCode); } }; if (! stdoutIsFlushed) { process.stdout.on('drain', exitIfFlushed); } if (! stderrIsFlushed) { process.stderr.on('drain', exitIfFlushed); } exitIfFlushed(null); } } // vim: set noexpandtab: