UNPKG

prepack

Version:

Execute a JS bundle, serialize global state and side effects to a snapshot that can be quickly restored.

599 lines (464 loc) 15.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.UISession = void 0; var _readline = _interopRequireDefault(require("readline")); var _child_process = _interopRequireDefault(require("child_process")); var DebugProtocol = _interopRequireWildcard(require("vscode-debugprotocol")); var _DataHandler = require("./DataHandler.js"); var _DebuggerConstants = require("./../common/DebuggerConstants"); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ //separator for messages according to the protocol const TWO_CRLF = "\r\n\r\n"; /* Represents one debugging session in the CLI. * Read in user input from the command line, parses the input into commands, * sends the commands to the adapter and process any responses */ class UISession { constructor(proc, args) { this._proc = proc; this._adapterPath = args.adapterPath; this._prepackRuntime = args.prepackRuntime; this._sourceFiles = args.sourceFiles; this._prepackArguments = args.prepackArguments; this._sequenceNum = 1; this._invalidCount = 0; this._dataHandler = new _DataHandler.DataHandler(); this._prepackWaiting = false; this._prepackLaunched = false; } // the parent (i.e. ui) process _startAdapter() { let adapterArgs = [this._adapterPath]; this._adapterProcess = _child_process.default.spawn("node", adapterArgs); this._proc.on("exit", () => { this.shutdown(); }); this._proc.on("SIGINT", () => { this.shutdown(); }); this._adapterProcess.stdout.on("data", data => { //handle the received data this._dataHandler.handleData(data, this._processMessage.bind(this)); }); this._adapterProcess.stderr.on("data", data => { console.error(data.toString()); this.shutdown(); }); } // called from data handler to process a received message _processMessage(message) { try { let msg = JSON.parse(message); if (msg.type === "event") { this._processEvent(msg); } else if (msg.type === "response") { this._processResponse(msg); } } catch (e) { console.error(e); console.error("Invalid message: " + message.slice(0, 1000)); } //ask the user for the next command if (this._prepackLaunched && this._prepackWaiting) { this._reader.question("(dbg) ", input => { this._dispatch(input); }); } } _processEvent(event) { if (event.event === "initialized") { // the adapter is ready to accept any persisted debug information // (e.g. persisted breakpoints from previous sessions). the CLI let configDoneArgs = {}; this._sendConfigDoneRequest(configDoneArgs); } else if (event.event === "output") { this._uiOutput("Prepack output:\n" + event.body.output); } else if (event.event === "terminated") { this._uiOutput("Prepack exited! Shutting down..."); this.shutdown(); } else if (event.event === "stopped") { this._prepackWaiting = true; if (event.body) { this._uiOutput(event.body.reason); } } } _processResponse(response) { if (response.command === "initialize") { this._processInitializeResponse(response); } else if (response.command === "launch") { this._processLaunchResponse(response); } else if (response.command === "threads") { this._processThreadsResponse(response); } else if (response.command === "stackTrace") { //flow doesn't have type refinement for interfaces, so must do a cast here this._processStackTraceResponse(response); } else if (response.command === "scopes") { this._processScopesResponse(response); } else if (response.command === "variables") { this._processVariablesResponse(response); } else if (response.command === "evaluate") { this._processEvaluateResponse(response); } } _processScopesResponse(response) { let scopes = response.body.scopes; for (const scope of scopes) { this._uiOutput(`${scope.name} ${scope.variablesReference}`); } } _processInitializeResponse(response) { let launchArgs = { prepackRuntime: this._prepackRuntime, sourceFiles: this._sourceFiles, prepackArguments: this._prepackArguments }; this._sendLaunchRequest(launchArgs); } _processLaunchResponse(response) { this._uiOutput("Prepack is ready"); this._prepackLaunched = true; this._prepackWaiting = true; // start reading requests from the user this._reader.question("(dbg) ", input => { this._dispatch(input); }); } _processStackTraceResponse(response) { let frames = response.body.stackFrames; for (const frame of frames) { if (frame.source && frame.source.path) { this._uiOutput(`${frame.id}: ${frame.name} ${frame.source.path} ${frame.line}:${frame.column}`); } else { this._uiOutput(`${frame.id}: ${frame.name} unknown source`); } } } _processThreadsResponse(response) { for (const thread of response.body.threads) { this._uiOutput(`${thread.id}: ${thread.name}`); } } _processVariablesResponse(response) { for (const variable of response.body.variables) { if (variable.variablesReference === 0) { // 0 means there are not more nested variables to return this._uiOutput(`${variable.name}: ${variable.value}`); } else { this._uiOutput(`${variable.name}: ${variable.value} ${variable.variablesReference}`); } } } _processEvaluateResponse(response) { let evalInfo = response.body; this._uiOutput("Type: " + (evalInfo.type || "unknown")); this._uiOutput(evalInfo.result); this._uiOutput("Variables Reference: " + evalInfo.variablesReference); } // execute a command if it is valid // returns whether the command was valid _executeCommand(input) { let parts = input.split(" "); let command = parts[0]; // for testing purposes, init and configDone are made into user commands // they can be done from the adapter without user input switch (command) { case "run": // format: run if (parts.length !== 1) return false; let continueArgs = { // Prepack will only have 1 thread, this argument will be ignored threadId: _DebuggerConstants.DebuggerConstants.PREPACK_THREAD_ID }; this._sendContinueRequest(continueArgs); break; case "breakpoint": // format: breakpoint add <filePath> <line> ?<column> if (parts.length !== 4 && parts.length !== 5) return false; if (parts[1] === "add") { let filePath = parts[2]; let line = parseInt(parts[3], 10); if (isNaN(line)) return false; let column = 0; if (parts.length === 5) { column = parseInt(parts[4], 10); if (isNaN(column)) return false; } this._sendBreakpointRequest(filePath, line, column); } break; case "stackframes": // format: stackFrames let stackFrameArgs = { // Prepack will only have 1 thread, this argument will be ignored threadId: _DebuggerConstants.DebuggerConstants.PREPACK_THREAD_ID }; this._sendStackFramesRequest(stackFrameArgs); break; case "threads": if (parts.length !== 1) return false; this._sendThreadsRequest(); break; case "scopes": if (parts.length !== 2) return false; let frameId = parseInt(parts[1], 10); if (isNaN(frameId)) return false; let scopesArgs = { frameId: frameId }; this._sendScopesRequest(scopesArgs); break; case "variables": if (parts.length !== 2) return false; let varRef = parseInt(parts[1], 10); if (isNaN(varRef)) return false; let variableArgs = { variablesReference: varRef }; this._sendVariablesRequest(variableArgs); break; case "stepInto": if (parts.length !== 1) return false; let stepIntoArgs = { threadId: _DebuggerConstants.DebuggerConstants.PREPACK_THREAD_ID }; this._sendStepIntoRequest(stepIntoArgs); break; case "stepOver": if (parts.length !== 1) return false; let stepOverArgs = { threadId: _DebuggerConstants.DebuggerConstants.PREPACK_THREAD_ID }; this._sendStepOverRequest(stepOverArgs); break; case "stepOut": if (parts.length !== 1) return false; let stepOutArgs = { threadId: _DebuggerConstants.DebuggerConstants.PREPACK_THREAD_ID }; this._sendStepOutRequest(stepOutArgs); break; case "eval": if (parts.length < 2) return false; let evalFrameId = parseInt(parts[1], 10); if (isNaN(evalFrameId)) { let expression = parts.slice(1).join(" "); let evaluateArgs = { expression: expression }; this._sendEvaluateRequest(evaluateArgs); } else { let expression = parts.slice(2).join(" "); let evaluateArgs = { expression: expression, frameId: evalFrameId }; this._sendEvaluateRequest(evaluateArgs); } break; default: // invalid command return false; } return true; } // parses the user input into a command and executes it _dispatch(input) { if (input === "exit") { this.shutdown(); } let success = this._executeCommand(input); if (!success) { // input was invalid this._invalidCount++; //prevent stack overflow from recursion if (this._invalidCount >= 10) { console.error("Too many invalid commands, shutting down..."); this.shutdown(); } console.error("Invalid command: " + input); this._reader.question("(dbg) ", line => { this._dispatch(line); }); } //reset the invalid command counter this._invalidCount = 0; } // tell the adapter about some configuration details _sendInitializeRequest(args) { let message = { type: "request", seq: this._sequenceNum, command: "initialize", arguments: args }; let json = JSON.stringify(message); this._packageAndSend(json); } // tell the adapter to start Prepack _sendLaunchRequest(args) { let message = { type: "request", seq: this._sequenceNum, command: "launch", arguments: args }; let json = JSON.stringify(message); this._packageAndSend(json); } // tell the adapter that configuration is done so it can expect other commands _sendConfigDoneRequest(args) { let message = { type: "request", seq: this._sequenceNum, command: "configurationDone", arguments: args }; let json = JSON.stringify(message); this._packageAndSend(json); } // tell the adapter to continue running Prepack _sendContinueRequest(args) { let message = { type: "request", seq: this._sequenceNum, command: "continue", arguments: args }; let json = JSON.stringify(message); this._packageAndSend(json); this._prepackWaiting = false; } _sendBreakpointRequest(filePath, line, column = 0) { let source = { path: filePath }; let breakpoint = { line: line, column: column }; let args = { source: source, breakpoints: [breakpoint] }; let message = { type: "request", seq: this._sequenceNum, command: "setBreakpoints", arguments: args }; let json = JSON.stringify(message); this._packageAndSend(json); } _sendStackFramesRequest(args) { let message = { type: "request", seq: this._sequenceNum, command: "stackTrace", arguments: args }; let json = JSON.stringify(message); this._packageAndSend(json); } _sendThreadsRequest() { let message = { type: "request", seq: this._sequenceNum, command: "threads" }; let json = JSON.stringify(message); this._packageAndSend(json); } _sendScopesRequest(args) { let message = { type: "request", seq: this._sequenceNum, command: "scopes", arguments: args }; let json = JSON.stringify(message); this._packageAndSend(json); } _sendVariablesRequest(args) { let message = { type: "request", seq: this._sequenceNum, command: "variables", arguments: args }; let json = JSON.stringify(message); this._packageAndSend(json); } _sendStepIntoRequest(args) { let message = { type: "request", seq: this._sequenceNum, command: "stepIn", arguments: args }; let json = JSON.stringify(message); this._packageAndSend(json); } _sendStepOverRequest(args) { let message = { type: "request", seq: this._sequenceNum, command: "next", arguments: args }; let json = JSON.stringify(message); this._packageAndSend(json); } _sendStepOutRequest(args) { let message = { type: "request", seq: this._sequenceNum, command: "stepOut", arguments: args }; let json = JSON.stringify(message); this._packageAndSend(json); } _sendEvaluateRequest(args) { let message = { type: "request", seq: this._sequenceNum, command: "evaluate", arguments: args }; let json = JSON.stringify(message); this._packageAndSend(json); } // write out a message to the adapter on stdout _packageAndSend(message) { // format: Content-Length: <length> separator <message> this._adapterProcess.stdin.write("Content-Length: " + Buffer.byteLength(message, "utf8") + TWO_CRLF + message, "utf8"); this._sequenceNum++; } _uiOutput(message) { console.log(message); } serve() { this._uiOutput("Debugger is starting up Prepack..."); // Set up the adapter connection this._startAdapter(); // send an initialize request to the adapter to fetch some configuration details let initArgs = { // a unique name for each UI (e.g Nuclide, VSCode, CLI) clientID: _DebuggerConstants.DebuggerConstants.CLI_CLIENTID, // a unique name for each adapter adapterID: "Prepack-Debugger-Adapter", supportsVariableType: true, supportsVariablePaging: false, supportsRunInTerminalRequest: false, pathFormat: "path" }; this._sendInitializeRequest(initArgs); this._reader = _readline.default.createInterface({ input: this._proc.stdin, output: this._proc.stdout }); } shutdown() { this._reader.close(); this._adapterProcess.kill(); this._proc.exit(0); } } exports.UISession = UISession; //# sourceMappingURL=UISession.js.map