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
JavaScript
"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