Reimplement debug adapter based on node2 adapter
This commit is contained in:
Родитель
dec006c021
Коммит
e45838cbf8
|
@ -7,7 +7,6 @@
|
|||
"type": "reactnative",
|
||||
"request": "launch",
|
||||
"platform": "android",
|
||||
"internalDebuggerPort": 9090,
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/.vscode/.react"
|
||||
},
|
||||
|
@ -18,9 +17,8 @@
|
|||
"request": "launch",
|
||||
"platform": "ios",
|
||||
"target": "iPhone 5s",
|
||||
"internalDebuggerPort": 9090,
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/.vscode/.react"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
465
package.json
465
package.json
|
@ -1,243 +1,232 @@
|
|||
{
|
||||
"name": "vscode-react-native",
|
||||
"displayName": "React Native Tools",
|
||||
"version": "0.2.5",
|
||||
"private": true,
|
||||
"publisher": "vsmobile",
|
||||
"icon": "images/icon.svg",
|
||||
"galleryBanner": {
|
||||
"color": "#3B3738",
|
||||
"theme": "dark"
|
||||
},
|
||||
"description": "Code-hinting, debugging and integrated commands for React Native",
|
||||
"bugs": "https://github.com/Microsoft/vscode-react-native/issues",
|
||||
"license": "SEE LICENSE IN LICENSE.txt",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/vscode-react-native"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^0.10.1"
|
||||
},
|
||||
"categories": [
|
||||
"Debuggers",
|
||||
"Other"
|
||||
"name": "vscode-react-native",
|
||||
"displayName": "React Native Tools",
|
||||
"version": "0.2.5",
|
||||
"private": true,
|
||||
"publisher": "vsmobile",
|
||||
"icon": "images/icon.svg",
|
||||
"galleryBanner": {
|
||||
"color": "#3B3738",
|
||||
"theme": "dark"
|
||||
},
|
||||
"description": "Code-hinting, debugging and integrated commands for React Native",
|
||||
"bugs": "https://github.com/Microsoft/vscode-react-native/issues",
|
||||
"license": "SEE LICENSE IN LICENSE.txt",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/vscode-react-native"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^0.10.1"
|
||||
},
|
||||
"categories": [
|
||||
"Debuggers",
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./out/extension/rn-extension",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "reactNative.runAndroid",
|
||||
"title": "React Native: Run Android"
|
||||
},
|
||||
{
|
||||
"command": "reactNative.runIos",
|
||||
"title": "React Native: Run iOS"
|
||||
},
|
||||
{
|
||||
"command": "reactNative.startPackager",
|
||||
"title": "React Native: Start Packager"
|
||||
},
|
||||
{
|
||||
"command": "reactNative.startExponentPackager",
|
||||
"title": "React Native: Start Exponent Packager"
|
||||
},
|
||||
{
|
||||
"command": "reactNative.stopPackager",
|
||||
"title": "React Native: Stop Packager"
|
||||
},
|
||||
{
|
||||
"command": "reactNative.restartPackager",
|
||||
"title": "React Native: Restart Packager"
|
||||
},
|
||||
{
|
||||
"command": "reactNative.publishToExpHost",
|
||||
"title": "React Native: Publish to Exponent"
|
||||
}
|
||||
],
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./out/extension/rn-extension",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "reactNative.runAndroid",
|
||||
"title": "React Native: Run Android"
|
||||
},
|
||||
{
|
||||
"command": "reactNative.runIos",
|
||||
"title": "React Native: Run iOS"
|
||||
},
|
||||
{
|
||||
"command": "reactNative.startPackager",
|
||||
"title": "React Native: Start Packager"
|
||||
},
|
||||
{
|
||||
"command": "reactNative.startExponentPackager",
|
||||
"title": "React Native: Start Exponent Packager"
|
||||
},
|
||||
{
|
||||
"command": "reactNative.stopPackager",
|
||||
"title": "React Native: Stop Packager"
|
||||
},
|
||||
{
|
||||
"command": "reactNative.restartPackager",
|
||||
"title": "React Native: Restart Packager"
|
||||
},
|
||||
{
|
||||
"command": "reactNative.publishToExpHost",
|
||||
"title": "React Native: Publish to Exponent"
|
||||
}
|
||||
"debuggers": [
|
||||
{
|
||||
"type": "reactnative",
|
||||
"label": "React Native",
|
||||
"program": "./out/debugger/reactNativeDebugEntryPoint.js",
|
||||
"runtime": "node",
|
||||
"enableBreakpointsFor": {
|
||||
"languageIds": [
|
||||
"javascript",
|
||||
"typescript",
|
||||
"javascriptreact",
|
||||
"typescriptreact"
|
||||
]
|
||||
},
|
||||
"initialConfigurations": [
|
||||
{
|
||||
"name": "Debug Android",
|
||||
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
|
||||
"type": "reactnative",
|
||||
"request": "launch",
|
||||
"platform": "android",
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/.vscode/.react"
|
||||
},
|
||||
{
|
||||
"name": "Debug iOS",
|
||||
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
|
||||
"type": "reactnative",
|
||||
"request": "launch",
|
||||
"platform": "ios",
|
||||
"target": "iPhone 5s",
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/.vscode/.react"
|
||||
},
|
||||
{
|
||||
"name": "Attach to packager",
|
||||
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
|
||||
"type": "reactnative",
|
||||
"request": "attach",
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/.vscode/.react"
|
||||
},
|
||||
{
|
||||
"name": "Debug in Exponent",
|
||||
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
|
||||
"type": "reactnative",
|
||||
"request": "launch",
|
||||
"platform": "exponent",
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/.vscode/.react"
|
||||
}
|
||||
],
|
||||
"debuggers": [
|
||||
{
|
||||
"type": "reactnative",
|
||||
"label": "React Native",
|
||||
"program": "./out/debugger/reactNativeDebugEntryPoint.js",
|
||||
"runtime": "node",
|
||||
"enableBreakpointsFor": {
|
||||
"languageIds": [
|
||||
"javascript",
|
||||
"typescript",
|
||||
"javascriptreact",
|
||||
"typescriptreact"
|
||||
]
|
||||
},
|
||||
"initialConfigurations": [
|
||||
{
|
||||
"name": "Debug Android",
|
||||
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
|
||||
"type": "reactnative",
|
||||
"request": "launch",
|
||||
"platform": "android",
|
||||
"internalDebuggerPort": 9090,
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/.vscode/.react"
|
||||
},
|
||||
{
|
||||
"name": "Debug iOS",
|
||||
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
|
||||
"type": "reactnative",
|
||||
"request": "launch",
|
||||
"platform": "ios",
|
||||
"target": "iPhone 5s",
|
||||
"internalDebuggerPort": 9090,
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/.vscode/.react"
|
||||
},
|
||||
{
|
||||
"name": "Attach to packager",
|
||||
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
|
||||
"type": "reactnative",
|
||||
"request": "attach",
|
||||
"internalDebuggerPort": 9090,
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/.vscode/.react"
|
||||
},
|
||||
{
|
||||
"name": "Debug in Exponent",
|
||||
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
|
||||
"type": "reactnative",
|
||||
"request": "launch",
|
||||
"platform": "exponent",
|
||||
"internalDebuggerPort": 9090,
|
||||
"sourceMaps": true,
|
||||
"outDir": "${workspaceRoot}/.vscode/.react"
|
||||
}
|
||||
],
|
||||
"configurationAttributes": {
|
||||
"attach": {
|
||||
"required": [
|
||||
"program"
|
||||
],
|
||||
"properties": {
|
||||
"program": {
|
||||
"type": "string",
|
||||
"description": "The path to launchReactNative.js in the vscode folder"
|
||||
},
|
||||
"internalDebuggerPort": {
|
||||
"type": "number",
|
||||
"description": "A port to be used to enable automatic reloading of breakpoints when sourcemaps change.",
|
||||
"default": 9090
|
||||
},
|
||||
"sourceMaps": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to use JavaScript source maps to map the generated bundled code back to its original sources",
|
||||
"default": false
|
||||
},
|
||||
"outDir": {
|
||||
"type": "string",
|
||||
"description": "The location of the generated JavaScript code (the bundle file). Normally this should be \"${workspaceRoot}/.vscode/.react\"",
|
||||
"default": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"launch": {
|
||||
"required": [
|
||||
"program",
|
||||
"platform"
|
||||
],
|
||||
"properties": {
|
||||
"platform": {
|
||||
"type": "string",
|
||||
"description": "The platform ('ios' or 'android') to target"
|
||||
},
|
||||
"program": {
|
||||
"type": "string",
|
||||
"description": "The path to launchReactNative.js in the vscode folder"
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "'simulator', 'device', or the name of the emulator to run on"
|
||||
},
|
||||
"internalDebuggerPort": {
|
||||
"type": "number",
|
||||
"description": "A port to be used to enable automatic reloading of breakpoints when sourcemaps change.",
|
||||
"default": 9090
|
||||
},
|
||||
"sourceMaps": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to use JavaScript source maps to map the generated bundled code back to its original sources",
|
||||
"default": false
|
||||
},
|
||||
"logCatArguments": {
|
||||
"type": "array",
|
||||
"description": "Arguments to be used for LogCat (The LogCat output will appear on an Output Channel). It can either be an array such as: [\":S\", \"ReactNative:V\", \"ReactNativeJS:V\"] or a string such as \":S ReactNative:V ReactNativeJS:V\"",
|
||||
"default": [
|
||||
"*:S",
|
||||
"ReactNative:V",
|
||||
"ReactNativeJS:V"
|
||||
]
|
||||
},
|
||||
"outDir": {
|
||||
"type": "string",
|
||||
"description": "The location of the generated JavaScript code (the bundle file). Normally this should be \"${workspaceRoot}/.vscode/.react\"",
|
||||
"default": null
|
||||
},
|
||||
"iosRelativeProjectPath": {
|
||||
"type": "string",
|
||||
"description": "Relative path to the ios/ folder, if it is not located on the project root.",
|
||||
"default": "ios"
|
||||
},
|
||||
"variant": {
|
||||
"type": "string",
|
||||
"description": "A variant to be passed to react-native run-android, e.g. 'devDebug' to specify --variant=devDebug"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"configurationAttributes": {
|
||||
"attach": {
|
||||
"required": [
|
||||
"program"
|
||||
],
|
||||
"properties": {
|
||||
"program": {
|
||||
"type": "string",
|
||||
"description": "The path to launchReactNative.js in the vscode folder"
|
||||
},
|
||||
"sourceMaps": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to use JavaScript source maps to map the generated bundled code back to its original sources",
|
||||
"default": false
|
||||
},
|
||||
"outDir": {
|
||||
"type": "string",
|
||||
"description": "The location of the generated JavaScript code (the bundle file). Normally this should be \"${workspaceRoot}/.vscode/.react\"",
|
||||
"default": null
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node node_modules/react-native/local-cli/cli.js start",
|
||||
"compile": "node ./node_modules/vscode/bin/compile -watch -p ./",
|
||||
"vscode:prepublish": "gulp",
|
||||
"test": "node ./node_modules/vscode/bin/test",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install"
|
||||
},
|
||||
"dependencies": {
|
||||
"extract-opts": "2.2.0",
|
||||
"flatten-source-map": "0.0.2",
|
||||
"options": "0.0.6",
|
||||
"q": "1.4.1",
|
||||
"semver": "5.1.0",
|
||||
"strip-json-comments": "2.0.1",
|
||||
"typechecker": "2.0.8",
|
||||
"ultron": "1.0.2",
|
||||
"vscode-extension-telemetry": "0.0.5",
|
||||
"ws": "1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"del": "^2.2.0",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-mocha": "^2.2.0",
|
||||
"gulp-preprocess": "^2.0.0",
|
||||
"gulp-sourcemaps": "^1.6.0",
|
||||
"gulp-tslint": "^4.3.3",
|
||||
"gulp-typescript": "^2.12.1",
|
||||
"gulp-util": "^3.0.7",
|
||||
"minimist": "^1.2.0",
|
||||
"mocha": "^2.4.5",
|
||||
"mocha-teamcity-reporter": "^1.0.0",
|
||||
"mock-fs": "^3.8.0",
|
||||
"run-sequence": "^1.1.5",
|
||||
"should": "^8.3.0",
|
||||
"sinon": "^1.17.3",
|
||||
"source-map-support": "^0.4.0",
|
||||
"through2": "^2.0.1",
|
||||
"tslint": "^3.6.0",
|
||||
"typescript": "^1.8.9",
|
||||
"vsce": "^1.3.0",
|
||||
"vscode": "^0.11.14"
|
||||
}
|
||||
},
|
||||
"launch": {
|
||||
"required": [
|
||||
"program",
|
||||
"platform"
|
||||
],
|
||||
"properties": {
|
||||
"platform": {
|
||||
"type": "string",
|
||||
"description": "The platform ('ios' or 'android') to target"
|
||||
},
|
||||
"program": {
|
||||
"type": "string",
|
||||
"description": "The path to launchReactNative.js in the vscode folder"
|
||||
},
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "'simulator', 'device', or the name of the emulator to run on"
|
||||
},
|
||||
"sourceMaps": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to use JavaScript source maps to map the generated bundled code back to its original sources",
|
||||
"default": false
|
||||
},
|
||||
"logCatArguments": {
|
||||
"type": "array",
|
||||
"description": "Arguments to be used for LogCat (The LogCat output will appear on an Output Channel). It can either be an array such as: [\":S\", \"ReactNative:V\", \"ReactNativeJS:V\"] or a string such as \":S ReactNative:V ReactNativeJS:V\"",
|
||||
"default": [
|
||||
"*:S",
|
||||
"ReactNative:V",
|
||||
"ReactNativeJS:V"
|
||||
]
|
||||
},
|
||||
"outDir": {
|
||||
"type": "string",
|
||||
"description": "The location of the generated JavaScript code (the bundle file). Normally this should be \"${workspaceRoot}/.vscode/.react\"",
|
||||
"default": null
|
||||
},
|
||||
"iosRelativeProjectPath": {
|
||||
"type": "string",
|
||||
"description": "Relative path to the ios/ folder, if it is not located on the project root.",
|
||||
"default": "ios"
|
||||
},
|
||||
"variant": {
|
||||
"type": "string",
|
||||
"description": "A variant to be passed to react-native run-android, e.g. 'devDebug' to specify --variant=devDebug"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node node_modules/react-native/local-cli/cli.js start",
|
||||
"compile": "node ./node_modules/vscode/bin/compile -watch -p ./",
|
||||
"vscode:prepublish": "gulp",
|
||||
"test": "node ./node_modules/vscode/bin/test",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install"
|
||||
},
|
||||
"dependencies": {
|
||||
"extract-opts": "2.2.0",
|
||||
"flatten-source-map": "0.0.2",
|
||||
"options": "0.0.6",
|
||||
"q": "1.4.1",
|
||||
"semver": "5.1.0",
|
||||
"strip-json-comments": "2.0.1",
|
||||
"typechecker": "2.0.8",
|
||||
"ultron": "1.0.2",
|
||||
"vscode-extension-telemetry": "0.0.5",
|
||||
"ws": "1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"del": "^2.2.0",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-mocha": "^2.2.0",
|
||||
"gulp-preprocess": "^2.0.0",
|
||||
"gulp-sourcemaps": "^1.6.0",
|
||||
"gulp-tslint": "^4.3.3",
|
||||
"gulp-typescript": "^2.12.1",
|
||||
"gulp-util": "^3.0.7",
|
||||
"minimist": "^1.2.0",
|
||||
"mocha": "^2.4.5",
|
||||
"mocha-teamcity-reporter": "^1.0.0",
|
||||
"mock-fs": "^3.8.0",
|
||||
"run-sequence": "^1.1.5",
|
||||
"should": "^8.3.0",
|
||||
"sinon": "^1.17.3",
|
||||
"source-map-support": "^0.4.0",
|
||||
"through2": "^2.0.1",
|
||||
"tslint": "^3.6.0",
|
||||
"typescript": "^1.8.9",
|
||||
"vsce": "^1.3.0",
|
||||
"vscode": "^0.11.14"
|
||||
},
|
||||
"extensionDependencies": [
|
||||
"ms-vscode.node-debug2"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -103,10 +103,10 @@ export class StreamLogger implements ILogger {
|
|||
}
|
||||
|
||||
export class NodeDebugAdapterLogger implements ILogger {
|
||||
private debugSession: NodeDebugSession;
|
||||
private debugAdapterPackage: typeof VSCodeDebugAdapter;
|
||||
private debugSession: ChromeDebuggerCorePackage.ChromeDebugSession;
|
||||
private debugAdapterPackage: typeof VSCodeDebugAdapterPackage;
|
||||
|
||||
public constructor(adapterPackage: typeof VSCodeDebugAdapter, debugSession: NodeDebugSession) {
|
||||
public constructor(adapterPackage: typeof VSCodeDebugAdapterPackage, debugSession: ChromeDebuggerCorePackage.ChromeDebugSession) {
|
||||
this.debugAdapterPackage = adapterPackage;
|
||||
this.debugSession = debugSession;
|
||||
}
|
||||
|
@ -148,4 +148,4 @@ export class NodeDebugAdapterLogger implements ILogger {
|
|||
// Do nothing
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import * as vm from "vm";
|
|||
import * as Q from "q";
|
||||
import * as path from "path";
|
||||
import * as WebSocket from "ws";
|
||||
import { EventEmitter } from "events";
|
||||
import {ScriptImporter} from "./scriptImporter";
|
||||
import {Packager} from "../common/packager";
|
||||
import {ErrorHelper} from "../common/error/errorHelper";
|
||||
|
@ -32,8 +33,9 @@ interface DebuggerWorkerSandbox {
|
|||
postMessageArgument: RNAppMessage; // We use this argument to pass messages to the worker
|
||||
}
|
||||
|
||||
interface RNAppMessage {
|
||||
export interface RNAppMessage {
|
||||
method: string;
|
||||
url?: string;
|
||||
// These objects have also other properties but that we don't currently use
|
||||
}
|
||||
|
||||
|
@ -41,7 +43,7 @@ function printDebuggingError(message: string, reason: any) {
|
|||
Log.logWarning(ErrorHelper.getNestedWarning(reason, `${message}. Debugging won't work: Try reloading the JS from inside the app, or Reconnect the VS Code debugger`));
|
||||
}
|
||||
|
||||
export class SandboxedAppWorker {
|
||||
export class SandboxedAppWorker implements IDebuggeeWorker {
|
||||
/** This class will run the RN App logic inside a sandbox. The framework to run the logic is provided by the file
|
||||
* debuggerWorker.js (designed to run on a WebWorker). We load that file inside a sandbox, and then we use the
|
||||
* PROCESS_MESSAGE_INSIDE_SANDBOX script to execute the logic to respond to a message inside the sandbox.
|
||||
|
@ -51,7 +53,6 @@ export class SandboxedAppWorker {
|
|||
*/
|
||||
private packagerPort: number;
|
||||
private sourcesStoragePath: string;
|
||||
private debugAdapterPort: number;
|
||||
private postReplyToApp: (message: any) => void;
|
||||
|
||||
private sandbox: DebuggerWorkerSandbox;
|
||||
|
@ -65,13 +66,12 @@ export class SandboxedAppWorker {
|
|||
|
||||
private static PROCESS_MESSAGE_INSIDE_SANDBOX = "onmessage({ data: postMessageArgument });";
|
||||
|
||||
constructor(packagerPort: number, sourcesStoragePath: string, debugAdapterPort: number, postReplyToApp: (message: any) => void, {
|
||||
constructor(packagerPort: number, sourcesStoragePath: string, postReplyToApp: (message: any) => void, {
|
||||
nodeFileSystem = new FileSystem(),
|
||||
scriptImporter = new ScriptImporter(packagerPort, sourcesStoragePath),
|
||||
} = {}) {
|
||||
this.packagerPort = packagerPort;
|
||||
this.sourcesStoragePath = sourcesStoragePath;
|
||||
this.debugAdapterPort = debugAdapterPort;
|
||||
this.postReplyToApp = postReplyToApp;
|
||||
this.scriptToReceiveMessageInSandbox = new vm.Script(SandboxedAppWorker.PROCESS_MESSAGE_INSIDE_SANDBOX);
|
||||
|
||||
|
@ -114,6 +114,8 @@ export class SandboxedAppWorker {
|
|||
this.runInSandbox(scriptToRunPath, "var onmessage = null; " + fileContents));
|
||||
}
|
||||
|
||||
public stop() { /* no-op */ }
|
||||
|
||||
public postMessage(object: RNAppMessage): void {
|
||||
this.sandbox.postMessageArgument = object;
|
||||
this.scriptToReceiveMessageInSandbox.runInContext(this.sandboxContext);
|
||||
|
@ -171,7 +173,7 @@ export class SandboxedAppWorker {
|
|||
this.pendingScriptImport = defer.promise;
|
||||
|
||||
// The next line converts to any due to the incorrect typing on node.d.ts of vm.runInThisContext
|
||||
this.scriptImporter.downloadAppScript(url, this.debugAdapterPort)
|
||||
this.scriptImporter.downloadAppScript(url)
|
||||
.then(downloadedScript =>
|
||||
this.runInSandbox(downloadedScript.filepath, downloadedScript.contents))
|
||||
.done(() => {
|
||||
|
@ -191,7 +193,13 @@ export class SandboxedAppWorker {
|
|||
}
|
||||
}
|
||||
|
||||
export class MultipleLifetimesAppWorker {
|
||||
export interface IDebuggeeWorker {
|
||||
start(): Q.Promise<any>;
|
||||
stop(): void;
|
||||
postMessage(message: RNAppMessage): void;
|
||||
}
|
||||
|
||||
export class MultipleLifetimesAppWorker extends EventEmitter {
|
||||
/** This class will create a SandboxedAppWorker that will run the RN App logic, and then create a socket
|
||||
* and send the RN App messages to the SandboxedAppWorker. The only RN App message that this class handles
|
||||
* is the prepareJSRuntime, which we reply to the RN App that the sandbox was created successfully.
|
||||
|
@ -199,23 +207,25 @@ export class MultipleLifetimesAppWorker {
|
|||
*/
|
||||
private packagerPort: number;
|
||||
private sourcesStoragePath: string;
|
||||
private debugAdapterPort: number;
|
||||
private socketToApp: WebSocket;
|
||||
private singleLifetimeWorker: SandboxedAppWorker;
|
||||
private singleLifetimeWorker: IDebuggeeWorker;
|
||||
|
||||
private sandboxedAppConstructor: (storagePath: string, adapterPort: number, messageFunction: (message: any) => void) => SandboxedAppWorker;
|
||||
private sandboxedAppConstructor: (storagePath: string, messageFunction: (message: any) => void) => IDebuggeeWorker;
|
||||
private webSocketConstructor: (url: string) => WebSocket;
|
||||
|
||||
private executionLimiter = new ExecutionsLimiter();
|
||||
|
||||
constructor(packagerPort: number, sourcesStoragePath: string, debugAdapterPort: number, {
|
||||
sandboxedAppConstructor = (path: string, port: number, messageFunc: (message: any) => void) =>
|
||||
new SandboxedAppWorker(packagerPort, path, port, messageFunc),
|
||||
constructor(packagerPort: number, sourcesStoragePath: string, {
|
||||
sandboxedAppConstructor = (path: string, messageFunc: (message: any) => void) =>
|
||||
new SandboxedAppWorker(packagerPort, path, messageFunc),
|
||||
webSocketConstructor = (url: string) => new WebSocket(url),
|
||||
}: {
|
||||
sandboxedAppConstructor?: (path: string, messageFunc: (message: any) => void) => IDebuggeeWorker;
|
||||
webSocketConstructor?: (url: string) => WebSocket
|
||||
} = {}) {
|
||||
super();
|
||||
this.packagerPort = packagerPort;
|
||||
this.sourcesStoragePath = sourcesStoragePath;
|
||||
this.debugAdapterPort = debugAdapterPort;
|
||||
console.assert(!!this.sourcesStoragePath, "The sourcesStoragePath argument was null or empty");
|
||||
|
||||
this.sandboxedAppConstructor = sandboxedAppConstructor;
|
||||
|
@ -232,12 +242,30 @@ export class MultipleLifetimesAppWorker {
|
|||
});
|
||||
}
|
||||
|
||||
public stop() {
|
||||
if (this.socketToApp) {
|
||||
this.socketToApp.removeAllListeners();
|
||||
this.socketToApp.close();
|
||||
}
|
||||
|
||||
if (this.singleLifetimeWorker) {
|
||||
this.singleLifetimeWorker.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private startNewWorkerLifetime(): Q.Promise<void> {
|
||||
this.singleLifetimeWorker = this.sandboxedAppConstructor(this.sourcesStoragePath, this.debugAdapterPort, (message) => {
|
||||
if (this.singleLifetimeWorker) {
|
||||
return Q.resolve<void>(void 0);
|
||||
}
|
||||
|
||||
this.singleLifetimeWorker = this.sandboxedAppConstructor(this.sourcesStoragePath, (message) => {
|
||||
this.sendMessageToApp(message);
|
||||
});
|
||||
Log.logInternalMessage(LogLevel.Info, "A new app worker lifetime was created.");
|
||||
return this.singleLifetimeWorker.start();
|
||||
return this.singleLifetimeWorker.start()
|
||||
.then(startedEvent => {
|
||||
this.emit("connected", startedEvent);
|
||||
});
|
||||
}
|
||||
|
||||
private createSocketToApp(warnOnFailure: boolean = false): Q.Promise<void> {
|
||||
|
@ -299,6 +327,7 @@ export class MultipleLifetimesAppWorker {
|
|||
this.gotPrepareJSRuntime(object);
|
||||
} else if (object.method === "$disconnected") {
|
||||
// We need to shutdown the current app worker, and create a new lifetime
|
||||
this.singleLifetimeWorker.stop();
|
||||
this.singleLifetimeWorker = null;
|
||||
} else if (object.method) {
|
||||
// All the other messages are handled by the single lifetime worker
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import * as Q from "q";
|
||||
import * as path from "path";
|
||||
import * as child_process from "child_process";
|
||||
import {ScriptImporter} from "./scriptImporter";
|
||||
import {FileSystem} from "../common/node/fileSystem";
|
||||
|
||||
import { Log } from "../common/log/log";
|
||||
import { ErrorHelper } from "../common/error/errorHelper";
|
||||
import { IDebuggeeWorker, RNAppMessage } from "./appWorker";
|
||||
|
||||
// tslint:disable-next-line:align
|
||||
const WORKER_BOOTSTRAP = `
|
||||
// Hacks to avoid accessing unitialized variables
|
||||
var onmessage=null, self=global;
|
||||
// Avoid Node's GLOBAL deprecation warning
|
||||
global.GLOBAL=global;
|
||||
process.on("message", function(message){
|
||||
if (onmessage) onmessage(message);
|
||||
});
|
||||
postMessage = function(message){
|
||||
process.send(message);
|
||||
};
|
||||
importScripts = function(scriptUrl){
|
||||
var scriptCode = require("fs").readFileSync(scriptUrl, "utf8");
|
||||
require("vm").runInThisContext(scriptCode, { filename: scriptUrl });
|
||||
};`;
|
||||
|
||||
const WORKER_DONE = `// Notify debugger that we're done with loading
|
||||
// and started listening for IPC messages
|
||||
postMessage({workerLoaded:true});`;
|
||||
|
||||
function printDebuggingError(message: string, reason: any) {
|
||||
Log.logWarning(ErrorHelper.getNestedWarning(reason, `${message}. Debugging won't work: Try reloading the JS from inside the app, or Reconnect the VS Code debugger`));
|
||||
}
|
||||
|
||||
// TODO: Add more logging
|
||||
// TODO: Replace all console logging with Log calls
|
||||
|
||||
/** This class will run the RN App logic inside a forked Node process. The framework to run the logic is provided by the file
|
||||
* debuggerWorker.js (designed to run on a WebWorker). We add a couple of tweaks (mostly to polyfill WebWorker API) to that
|
||||
* file and load it inside of a process.
|
||||
* On this side we listen to IPC messages and either respond to them or redirect them to packager via MultipleLifetimeAppWorker's
|
||||
* instance. We also intercept packager's signal to load the bundle's code and mutate the message with path to file we've downloaded
|
||||
* to let importScripts function take this file.
|
||||
*/
|
||||
export class ForkedAppWorker implements IDebuggeeWorker {
|
||||
|
||||
private nodeFileSystem: FileSystem = new FileSystem();
|
||||
private scriptImporter: ScriptImporter;
|
||||
private debuggeeProcess: child_process.ChildProcess = null;
|
||||
/** A deferred that we use to make sure that worker has been loaded completely defore start sending IPC messages */
|
||||
private workerLoaded = Q.defer<void>();
|
||||
|
||||
constructor(
|
||||
private packagerPort: number,
|
||||
private sourcesStoragePath: string,
|
||||
private postReplyToApp: (message: any) => void
|
||||
) {
|
||||
this.scriptImporter = new ScriptImporter(packagerPort, sourcesStoragePath);
|
||||
}
|
||||
|
||||
public stop() {
|
||||
if (this.debuggeeProcess) {
|
||||
console.log(`KILLING ${this.debuggeeProcess.pid} !!!!!!!!!!!!!!!!!!!!!!!!!!`);
|
||||
this.debuggeeProcess.kill();
|
||||
this.debuggeeProcess = null;
|
||||
}
|
||||
}
|
||||
|
||||
public start(): Q.Promise<number> {
|
||||
let scriptToRunPath = path.resolve(this.sourcesStoragePath, ScriptImporter.DEBUGGER_WORKER_FILENAME);
|
||||
|
||||
return this.scriptImporter.downloadDebuggerWorker(this.sourcesStoragePath)
|
||||
.then(() => this.nodeFileSystem.readFile(scriptToRunPath, "utf8"))
|
||||
.then((workerContent: string) => {
|
||||
// Add our customizations to debugger worker to get it working smoothly
|
||||
// in Node env and polyfill WebWorkers API over Node's IPC.
|
||||
const modifiedDebuggeeContent = [WORKER_BOOTSTRAP, workerContent, WORKER_DONE].join("\n");
|
||||
return this.nodeFileSystem.writeFile(scriptToRunPath, modifiedDebuggeeContent);
|
||||
})
|
||||
.then(() => {
|
||||
// Create a random port to use for debugging
|
||||
const port = Math.round(Math.random() * 40000 + 3000);
|
||||
// Start forked Node process in debugging mode
|
||||
this.debuggeeProcess = child_process.fork(scriptToRunPath, [], {
|
||||
// Note that we set --debug-brk flag to pause the process on the first line - this is
|
||||
// required for debug adapter to set the breakpoints BEFORE the debuggee has started.
|
||||
// The adapter will continue execution once it's done with breakpoints.
|
||||
execArgv: [`--inspect=${port}`, "--debug-brk"],
|
||||
})
|
||||
// TODO: shouldn't this be of RNAppMessage?
|
||||
.on("message", (message: any) => {
|
||||
// 'workerLoaded' is a special message that indicates that worker is done with loading.
|
||||
// We need to wait for it before doing any IPC because process.send doesn't seems to care
|
||||
// about whether the messahe has been received or not and the first messages are often get
|
||||
// discarded by spawned process
|
||||
if (message && message.workerLoaded) {
|
||||
this.workerLoaded.resolve(void 0);
|
||||
return;
|
||||
}
|
||||
|
||||
this.postReplyToApp(message);
|
||||
});
|
||||
|
||||
console.log(`SPAWNED ${this.debuggeeProcess.pid} !!!!!!!!!!!!!!!!!!!!!!!!!!`);
|
||||
|
||||
// Resolve with port debugger server is listening on
|
||||
// This will be sent to subscribers of MLAppWorker in "connected" event
|
||||
return port;
|
||||
});
|
||||
}
|
||||
|
||||
public postMessage(rnMessage: RNAppMessage): void {
|
||||
// Before sending messages, make sure that the worker is loaded
|
||||
this.workerLoaded.promise
|
||||
.then(() => {
|
||||
if (rnMessage.method !== "executeApplicationScript") return Q.resolve(rnMessage);
|
||||
|
||||
// When packager asks worker to load bundle we download that bundle first
|
||||
// and then set url field to point to that downloaded bundle, so the worker
|
||||
// will take our modified bundle
|
||||
return this.scriptImporter.downloadAppScript(rnMessage.url)
|
||||
.then(downloadedScript => Object.assign({}, rnMessage, { url: downloadedScript.filepath }));
|
||||
})
|
||||
.done((message: RNAppMessage) => this.debuggeeProcess.send({ data: message }),
|
||||
reason => printDebuggingError(`Couldn't import script at <${rnMessage.url}>`, reason));
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
import {MultipleLifetimesAppWorker} from "./appWorker";
|
||||
import {ErrorHelper} from "../common/error/errorHelper";
|
||||
import {InternalErrorCode} from "../common/error/internalErrorCode";
|
||||
import {ScriptImporter} from "./scriptImporter";
|
||||
import {TelemetryHelper} from "../common/telemetryHelper";
|
||||
import {Log} from "../common/log/log";
|
||||
import {RemoteExtension} from "../common/remoteExtension";
|
||||
import {EntryPointHandler, ProcessType} from "../common/entryPointHandler";
|
||||
|
||||
export class Launcher {
|
||||
private workspaceRootPath: string;
|
||||
private projectRootPath: string;
|
||||
private remoteExtension: RemoteExtension;
|
||||
|
||||
constructor(workspaceRootPath: string, projectRootPath: string) {
|
||||
this.workspaceRootPath = workspaceRootPath;
|
||||
this.projectRootPath = projectRootPath;
|
||||
this.remoteExtension = RemoteExtension.atProjectRootPath(this.projectRootPath);
|
||||
}
|
||||
|
||||
public launch(): void {
|
||||
const debugAdapterPort = parseInt(process.argv[2], 10) || 9090;
|
||||
// Enable telemetry
|
||||
new EntryPointHandler(ProcessType.Debugee).runApp("react-native-debug-process", () => this.getAppVersion(),
|
||||
ErrorHelper.getInternalError(InternalErrorCode.DebuggingFailed), this.projectRootPath, () => {
|
||||
return TelemetryHelper.generate("launch", (generator) => {
|
||||
const sourcesStoragePath = path.join(this.workspaceRootPath, ".vscode", ".react");
|
||||
return this.remoteExtension.getPackagerPort().then(packagerPort => {
|
||||
let scriptImporter = new ScriptImporter(packagerPort, sourcesStoragePath);
|
||||
return scriptImporter.downloadDebuggerWorker(sourcesStoragePath).then(() => {
|
||||
Log.logMessage("Downloaded debuggerWorker.js (Logic to run the React Native app) from the Packager.");
|
||||
}).then(() => {
|
||||
generator.step("Starting App Worker");
|
||||
Log.logMessage("Starting debugger app worker.");
|
||||
return new MultipleLifetimesAppWorker(packagerPort, sourcesStoragePath, debugAdapterPort).start();
|
||||
}).then(() =>
|
||||
Log.logMessage("Debugging session started successfully."));
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private getAppVersion() {
|
||||
return JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "package.json"), "utf-8")).version;
|
||||
}
|
||||
}
|
|
@ -4,48 +4,52 @@
|
|||
// These typings do not reflect the typings as intended to be used
|
||||
// but rather as they exist in truth, so we can reach into the internals
|
||||
// and access what we need.
|
||||
declare module VSCodeDebugAdapter {
|
||||
declare module VSCodeDebugAdapterPackage {
|
||||
class DebugSession {
|
||||
public static run: Function;
|
||||
public sendEvent(event: VSCodeDebugAdapter.InitializedEvent): void;
|
||||
public start(input: any, output: any): void;
|
||||
public launchRequest(response: any, args: any): void;
|
||||
public attachRequest(response: any, args: any): void;
|
||||
public disconnectRequest(response: any, args: any): void;
|
||||
public static run(debugSession: typeof DebugSession): void;
|
||||
}
|
||||
class InitializedEvent {
|
||||
class InitializedEvent extends Event {
|
||||
constructor();
|
||||
}
|
||||
class OutputEvent {
|
||||
class OutputEvent extends Event {
|
||||
constructor(message: string, destination?: string);
|
||||
}
|
||||
class TerminatedEvent {
|
||||
class TerminatedEvent extends Event {
|
||||
constructor();
|
||||
}
|
||||
class Event {
|
||||
public event: string;
|
||||
public body: any;
|
||||
}
|
||||
interface Request {
|
||||
command: string;
|
||||
arguments?: any;
|
||||
}
|
||||
}
|
||||
|
||||
declare class SourceMaps {
|
||||
constructor(session: NodeDebugSession, generatedCodeDirectory: string, generatedCodeGlobs: string[]);
|
||||
declare module ChromeDebuggerCorePackage {
|
||||
abstract class ChromeDebugAdapter {
|
||||
protected _attachMode: boolean;
|
||||
protected doAttach(port: number, targetUrl?: string, address?: string, timeout?: number): Promise<void>;
|
||||
}
|
||||
|
||||
interface IChromeDebugSessionOpts {
|
||||
logFilePath?: string;
|
||||
adapter: typeof ChromeDebugAdapter;
|
||||
extensionName: string;
|
||||
}
|
||||
|
||||
class ChromeDebugSession extends VSCodeDebugAdapterPackage.DebugSession {
|
||||
protected _debugAdapter: any;
|
||||
constructor(debuggerLinesAndColumnsStartAt1?: boolean, isServer?: boolean, opts?: IChromeDebugSessionOpts);
|
||||
public sendEvent(event: VSCodeDebugAdapterPackage.InitializedEvent): void;
|
||||
protected dispatchRequest(request: { command: string }): void;
|
||||
}
|
||||
}
|
||||
|
||||
declare class NodeDebugSession extends VSCodeDebugAdapter.DebugSession {
|
||||
public _sourceMaps: SourceMaps;
|
||||
declare module Node2DebugAdapterPackage {
|
||||
class Node2DebugAdapter extends ChromeDebuggerCorePackage.ChromeDebugAdapter {
|
||||
protected doAttach(port: number, targetUrl?: string, address?: string, timeout?: number): Promise<void>
|
||||
}
|
||||
}
|
||||
|
||||
interface ILaunchRequestArgs {
|
||||
platform: string;
|
||||
target?: string;
|
||||
internalDebuggerPort?: any;
|
||||
iosRelativeProjectPath?: string;
|
||||
args: string[];
|
||||
logCatArguments: any;
|
||||
program: string;
|
||||
variant?: string;
|
||||
}
|
||||
|
||||
interface IAttachRequestArgs {
|
||||
internalDebuggerPort?: any;
|
||||
args: string[];
|
||||
program: string;
|
||||
platform: string;
|
||||
}
|
||||
|
|
|
@ -1,276 +1,254 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import * as Q from "q";
|
||||
import * as path from "path";
|
||||
import * as http from "http";
|
||||
import * as fs from "fs";
|
||||
import stripJsonComments = require("strip-json-comments");
|
||||
|
||||
import {Telemetry} from "../common/telemetry";
|
||||
import {TelemetryHelper} from "../common/telemetryHelper";
|
||||
import {RemoteExtension} from "../common/remoteExtension";
|
||||
import {IOSPlatform} from "./ios/iOSPlatform";
|
||||
import {PlatformResolver} from "./platformResolver";
|
||||
import {IRunOptions} from "../common/launchArgs";
|
||||
import {TargetPlatformHelper} from "../common/targetPlatformHelper";
|
||||
import {ExtensionTelemetryReporter, ReassignableTelemetryReporter} from "../common/telemetryReporters";
|
||||
import {NodeDebugAdapterLogger} from "../common/log/loggers";
|
||||
import {Log} from "../common/log/log";
|
||||
import {GeneralMobilePlatform} from "../common/generalMobilePlatform";
|
||||
|
||||
export class NodeDebugWrapper {
|
||||
private projectRootPath: string;
|
||||
private remoteExtension: RemoteExtension;
|
||||
private telemetryReporter: ReassignableTelemetryReporter;
|
||||
private appName: string;
|
||||
private version: string;
|
||||
private mobilePlatformOptions: IRunOptions;
|
||||
|
||||
private vscodeDebugAdapterPackage: typeof VSCodeDebugAdapter;
|
||||
private nodeDebugSession: typeof NodeDebugSession;
|
||||
private sourceMapsConstructor: typeof SourceMaps;
|
||||
private originalLaunchRequest: (response: any, args: any) => void;
|
||||
|
||||
public constructor(appName: string, version: string, telemetryReporter: ReassignableTelemetryReporter,
|
||||
debugAdapter: typeof VSCodeDebugAdapter, debugSession: typeof NodeDebugSession, sourceMaps: typeof SourceMaps) {
|
||||
|
||||
this.appName = appName;
|
||||
this.version = version;
|
||||
this.telemetryReporter = telemetryReporter;
|
||||
this.vscodeDebugAdapterPackage = debugAdapter;
|
||||
this.nodeDebugSession = debugSession;
|
||||
this.sourceMapsConstructor = sourceMaps;
|
||||
this.originalLaunchRequest = this.nodeDebugSession.prototype.launchRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls customize methods for all requests needed
|
||||
*/
|
||||
public customizeNodeAdapterRequests(): void {
|
||||
this.customizeLaunchRequest();
|
||||
this.customizeAttachRequest();
|
||||
this.customizeDisconnectRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Intecept the "launchRequest" instance method of NodeDebugSession to interpret arguments.
|
||||
* Launch should:
|
||||
* - Run the packager if needed
|
||||
* - Compile and run application
|
||||
* - Prewarm bundle
|
||||
*/
|
||||
private customizeLaunchRequest(): void {
|
||||
const nodeDebugWrapper = this;
|
||||
this.nodeDebugSession.prototype.launchRequest = function (request: any, args: ILaunchRequestArgs) {
|
||||
nodeDebugWrapper.requestSetup(this, args);
|
||||
nodeDebugWrapper.mobilePlatformOptions.target = args.target || "simulator";
|
||||
nodeDebugWrapper.mobilePlatformOptions.iosRelativeProjectPath = !nodeDebugWrapper.isNullOrUndefined(args.iosRelativeProjectPath) ?
|
||||
args.iosRelativeProjectPath :
|
||||
IOSPlatform.DEFAULT_IOS_PROJECT_RELATIVE_PATH;
|
||||
|
||||
// We add the parameter if it's defined (adapter crashes otherwise)
|
||||
if (!nodeDebugWrapper.isNullOrUndefined(args.logCatArguments)) {
|
||||
nodeDebugWrapper.mobilePlatformOptions.logCatArguments = [nodeDebugWrapper.parseLogCatArguments(args.logCatArguments)];
|
||||
}
|
||||
|
||||
if (!nodeDebugWrapper.isNullOrUndefined(args.variant)) {
|
||||
nodeDebugWrapper.mobilePlatformOptions.variant = args.variant;
|
||||
}
|
||||
|
||||
return TelemetryHelper.generate("launch", (generator) => {
|
||||
const resolver = new PlatformResolver();
|
||||
return nodeDebugWrapper.remoteExtension.getPackagerPort()
|
||||
.then(packagerPort => {
|
||||
nodeDebugWrapper.mobilePlatformOptions.packagerPort = packagerPort;
|
||||
const mobilePlatform = resolver.resolveMobilePlatform(args.platform, nodeDebugWrapper.mobilePlatformOptions);
|
||||
return Q({})
|
||||
.then(() => {
|
||||
generator.step("checkPlatformCompatibility");
|
||||
TargetPlatformHelper.checkTargetPlatformSupport(nodeDebugWrapper.mobilePlatformOptions.platform);
|
||||
generator.step("startPackager");
|
||||
return mobilePlatform.startPackager();
|
||||
})
|
||||
.then(() => {
|
||||
// We've seen that if we don't prewarm the bundle cache, the app fails on the first attempt to connect to the debugger logic
|
||||
// and the user needs to Reload JS manually. We prewarm it to prevent that issue
|
||||
generator.step("prewarmBundleCache");
|
||||
Log.logMessage("Prewarming bundle cache. This may take a while ...");
|
||||
return mobilePlatform.prewarmBundleCache();
|
||||
})
|
||||
.then(() => {
|
||||
generator.step("mobilePlatform.runApp");
|
||||
Log.logMessage("Building and running application.");
|
||||
return mobilePlatform.runApp();
|
||||
})
|
||||
.then(() =>
|
||||
nodeDebugWrapper.attachRequest(this, request, args, mobilePlatform));
|
||||
}).catch(error =>
|
||||
nodeDebugWrapper.bailOut(this, error.message));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Intecept the "attachRequest" instance method of NodeDebugSession to interpret arguments
|
||||
*/
|
||||
private customizeAttachRequest(): void {
|
||||
const nodeDebugWrapper = this;
|
||||
this.nodeDebugSession.prototype.attachRequest = function (request: any, args: IAttachRequestArgs) {
|
||||
nodeDebugWrapper.requestSetup(this, args);
|
||||
nodeDebugWrapper.attachRequest(this, request, args, new GeneralMobilePlatform(nodeDebugWrapper.mobilePlatformOptions));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Intecept the "disconnectRequest" instance method of NodeDebugSession to interpret arguments
|
||||
*/
|
||||
private customizeDisconnectRequest(): void {
|
||||
const originalRequest = this.nodeDebugSession.prototype.disconnectRequest;
|
||||
const nodeDebugWrapper = this;
|
||||
|
||||
this.nodeDebugSession.prototype.disconnectRequest = function (response: any, args: any): void {
|
||||
// First we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
|
||||
|
||||
if (nodeDebugWrapper.mobilePlatformOptions.platform === "android") {
|
||||
nodeDebugWrapper.remoteExtension.stopMonitoringLogcat()
|
||||
.catch(reason =>
|
||||
Log.logError(`WARNING: Couldn't stop monitoring logcat: ${reason.message || reason}\n`))
|
||||
.finally(() =>
|
||||
originalRequest.call(this, response, args));
|
||||
} else {
|
||||
originalRequest.call(this, response, args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the required setup for request customization
|
||||
* - Enables telemetry
|
||||
* - Sets up mobilePlatformOptions, remote extension and projectRootPath
|
||||
* - Starts debug server
|
||||
* - Create global logger
|
||||
*/
|
||||
private requestSetup(debugSession: NodeDebugSession, args: any) {
|
||||
this.projectRootPath = this.getProjectRoot(args);
|
||||
this.remoteExtension = RemoteExtension.atProjectRootPath(this.projectRootPath);
|
||||
this.mobilePlatformOptions = {
|
||||
projectRoot: this.projectRootPath,
|
||||
platform: args.platform,
|
||||
};
|
||||
|
||||
// Start to send telemetry
|
||||
this.telemetryReporter.reassignTo(new ExtensionTelemetryReporter(
|
||||
this.appName, this.version, Telemetry.APPINSIGHTS_INSTRUMENTATIONKEY, this.projectRootPath));
|
||||
|
||||
// Create a server waiting for messages to re-initialize the debug session;
|
||||
const debugServerListeningPort = this.createReinitializeServer(debugSession, args.internalDebuggerPort, args.outDir);
|
||||
args.args = [debugServerListeningPort.toString()];
|
||||
|
||||
Log.SetGlobalLogger(new NodeDebugAdapterLogger(this.vscodeDebugAdapterPackage, debugSession));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs logic needed to attach.
|
||||
* Attach should:
|
||||
* - Enable js debugging
|
||||
*/
|
||||
private attachRequest(debugSession: NodeDebugSession, request: any, args: any, mobilePlatform: any): Q.Promise<void> {
|
||||
return TelemetryHelper.generate("attach", (generator) => {
|
||||
return Q({})
|
||||
.then(() => {
|
||||
generator.step("mobilePlatform.enableJSDebuggingMode");
|
||||
if (mobilePlatform) {
|
||||
return mobilePlatform.enableJSDebuggingMode();
|
||||
} else {
|
||||
Log.logMessage("Debugger ready. Enable remote debugging in app.");
|
||||
}
|
||||
}).then(() =>
|
||||
this.originalLaunchRequest.call(debugSession, request, args))
|
||||
.catch(error =>
|
||||
this.bailOut(debugSession, error.message));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates internal debug server and returns the port that the server is hook up into.
|
||||
*/
|
||||
private createReinitializeServer(debugSession: NodeDebugSession, internalDebuggerPort: string, sourcesDir: string): number {
|
||||
// Create the server
|
||||
const server = http.createServer((req, res) => {
|
||||
res.statusCode = 404;
|
||||
if (req.url === "/refreshBreakpoints") {
|
||||
res.statusCode = 200;
|
||||
if (debugSession) {
|
||||
const sourceMaps = debugSession._sourceMaps;
|
||||
if (sourceMaps) {
|
||||
// Flush any cached source maps
|
||||
// Rather than cleaning internal caches we recreate
|
||||
// SourceMaps to add downloaded bundle map to cache
|
||||
const bundlePattern = path.join(sourcesDir, "*.bundle");
|
||||
const sourceMaps = new this.sourceMapsConstructor(debugSession, sourcesDir, [bundlePattern]);
|
||||
debugSession._sourceMaps = sourceMaps;
|
||||
}
|
||||
// Send an "initialized" event to trigger breakpoints to be re-sent
|
||||
debugSession.sendEvent(new this.vscodeDebugAdapterPackage.InitializedEvent());
|
||||
}
|
||||
}
|
||||
res.end();
|
||||
});
|
||||
|
||||
// Setup listen port and on error response
|
||||
const port = parseInt(internalDebuggerPort, 10) || 9090;
|
||||
|
||||
server.listen(port);
|
||||
server.on("error", (err: Error) => {
|
||||
TelemetryHelper.sendSimpleEvent("reinitializeServerError");
|
||||
Log.logError("Error in debug adapter server: " + err.toString());
|
||||
Log.logMessage("Breakpoints may not update. Consider restarting and specifying a different 'internalDebuggerPort' in launch.json");
|
||||
});
|
||||
|
||||
// Return listen port
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs error to user and finishes the debugging process.
|
||||
*/
|
||||
private bailOut(debugSession: NodeDebugSession, message: string): void {
|
||||
Log.logError(`Could not debug. ${message}`);
|
||||
debugSession.sendEvent(new this.vscodeDebugAdapterPackage.TerminatedEvent());
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses log cat arguments to a string
|
||||
*/
|
||||
private parseLogCatArguments(userProvidedLogCatArguments: any): string {
|
||||
return Array.isArray(userProvidedLogCatArguments)
|
||||
? userProvidedLogCatArguments.join(" ") // If it's an array, we join the arguments
|
||||
: userProvidedLogCatArguments; // If not, we leave it as-is
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to know if a value is either null or undefined
|
||||
*/
|
||||
private isNullOrUndefined(value: any): boolean {
|
||||
return typeof value === "undefined" || value === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses settings.json file for workspace root property
|
||||
*/
|
||||
private getProjectRoot(args: any): string {
|
||||
try {
|
||||
let vsCodeRoot = path.resolve(args.program, "../..");
|
||||
let settingsPath = path.resolve(vsCodeRoot, ".vscode/settings.json");
|
||||
let settingsContent = fs.readFileSync(settingsPath, "utf8");
|
||||
settingsContent = stripJsonComments(settingsContent);
|
||||
let parsedSettings = JSON.parse(settingsContent);
|
||||
let projectRootPath = parsedSettings["react-native-tools"].projectRoot;
|
||||
return path.resolve(vsCodeRoot, projectRootPath);
|
||||
} catch (e) {
|
||||
return path.resolve(args.program, "../..");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
import * as Q from "q";
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import stripJsonComments = require("strip-json-comments");
|
||||
|
||||
import {Telemetry} from "../common/telemetry";
|
||||
import {TelemetryHelper} from "../common/telemetryHelper";
|
||||
import {RemoteExtension} from "../common/remoteExtension";
|
||||
import {IOSPlatform} from "./ios/iOSPlatform";
|
||||
import {PlatformResolver} from "./platformResolver";
|
||||
import {IRunOptions} from "../common/launchArgs";
|
||||
import {TargetPlatformHelper} from "../common/targetPlatformHelper";
|
||||
import {ExtensionTelemetryReporter, ReassignableTelemetryReporter} from "../common/telemetryReporters";
|
||||
import {NodeDebugAdapterLogger} from "../common/log/loggers";
|
||||
import {Log} from "../common/log/log";
|
||||
import {GeneralMobilePlatform} from "../common/generalMobilePlatform";
|
||||
|
||||
import { MultipleLifetimesAppWorker } from "./appWorker";
|
||||
import { ForkedAppWorker } from "./forkedAppWorker";
|
||||
|
||||
export function makeSession(debugSessionClass: typeof ChromeDebuggerCorePackage.ChromeDebugSession,
|
||||
debugSessionOpts: ChromeDebuggerCorePackage.IChromeDebugSessionOpts,
|
||||
debugAdapterPackage: typeof VSCodeDebugAdapterPackage,
|
||||
telemetryReporter: ReassignableTelemetryReporter,
|
||||
appName: string, version: string): typeof ChromeDebuggerCorePackage.ChromeDebugSession {
|
||||
|
||||
return class extends debugSessionClass {
|
||||
|
||||
private projectRootPath: string;
|
||||
private remoteExtension: RemoteExtension;
|
||||
private mobilePlatformOptions: IRunOptions;
|
||||
private appWorker: MultipleLifetimesAppWorker = null;
|
||||
private packagerPort: number;
|
||||
|
||||
constructor(debuggerLinesAndColumnsStartAt1?: boolean, isServer?: boolean) {
|
||||
super(debuggerLinesAndColumnsStartAt1, isServer, debugSessionOpts);
|
||||
}
|
||||
|
||||
// Override ChromeDebugSession's sendEvent to control what we will send to client
|
||||
public sendEvent(event: VSCodeDebugAdapterPackage.Event): void {
|
||||
// Do not send "terminated" events signaling about session's restart to client as it would cause it
|
||||
// to restart adapter's process, while we want to stay alive and don't want to interrupt connection
|
||||
// to packager. Also in this case we need to reinstantiate the debug adapter, as the current
|
||||
// implementation of ChromeDebugAdapter doesn't allow us to reattach to another debug target easily.
|
||||
// As of now it's easier to throw previous instance out and create a new one.
|
||||
if (event.event === "terminated" && event.body && event.body.restart === true) {
|
||||
// Casting debugAdapter to any to make this compile, as TS doesn't allow instantiating abstract classes
|
||||
this._debugAdapter = new (<any>debugSessionOpts.adapter)(debugSessionOpts, this);
|
||||
return;
|
||||
}
|
||||
|
||||
super.sendEvent(event);
|
||||
}
|
||||
|
||||
protected dispatchRequest(request: VSCodeDebugAdapterPackage.Request): void {
|
||||
if (request.command === "disconnect")
|
||||
return this.disconnect(request);
|
||||
|
||||
if (request.command === "attach")
|
||||
return this.attach(request);
|
||||
|
||||
if (request.command === "launch")
|
||||
return this.launch(request);
|
||||
|
||||
return super.dispatchRequest(request);
|
||||
}
|
||||
|
||||
private launch(request: VSCodeDebugAdapterPackage.Request): void {
|
||||
this.requestSetup(request.arguments);
|
||||
this.mobilePlatformOptions.target = request.arguments.target || "simulator";
|
||||
this.mobilePlatformOptions.iosRelativeProjectPath = !isNullOrUndefined(request.arguments.iosRelativeProjectPath) ?
|
||||
request.arguments.iosRelativeProjectPath :
|
||||
IOSPlatform.DEFAULT_IOS_PROJECT_RELATIVE_PATH;
|
||||
|
||||
// We add the parameter if it's defined (adapter crashes otherwise)
|
||||
if (!isNullOrUndefined(request.arguments.logCatArguments)) {
|
||||
this.mobilePlatformOptions.logCatArguments = [parseLogCatArguments(request.arguments.logCatArguments)];
|
||||
}
|
||||
|
||||
if (!isNullOrUndefined(request.arguments.variant)) {
|
||||
this.mobilePlatformOptions.variant = request.arguments.variant;
|
||||
}
|
||||
|
||||
TelemetryHelper.generate("launch", (generator) => {
|
||||
return this.remoteExtension.getPackagerPort()
|
||||
.then((packagerPort: number) => {
|
||||
this.packagerPort = packagerPort;
|
||||
this.mobilePlatformOptions.packagerPort = packagerPort;
|
||||
const mobilePlatform = new PlatformResolver()
|
||||
.resolveMobilePlatform(request.arguments.platform, this.mobilePlatformOptions);
|
||||
|
||||
generator.step("checkPlatformCompatibility");
|
||||
TargetPlatformHelper.checkTargetPlatformSupport(this.mobilePlatformOptions.platform);
|
||||
generator.step("startPackager");
|
||||
return mobilePlatform.startPackager()
|
||||
.then(() => {
|
||||
// We've seen that if we don't prewarm the bundle cache, the app fails on the first attempt to connect to the debugger logic
|
||||
// and the user needs to Reload JS manually. We prewarm it to prevent that issue
|
||||
generator.step("prewarmBundleCache");
|
||||
Log.logMessage("Prewarming bundle cache. This may take a while ...");
|
||||
return mobilePlatform.prewarmBundleCache();
|
||||
})
|
||||
.then(() => {
|
||||
generator.step("mobilePlatform.runApp");
|
||||
Log.logMessage("Building and running application.");
|
||||
return mobilePlatform.runApp();
|
||||
})
|
||||
.then(() => {
|
||||
return this.attachRequest(request, mobilePlatform);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private attach(request: VSCodeDebugAdapterPackage.Request): void {
|
||||
this.requestSetup(request.arguments);
|
||||
this.attachRequest(request);
|
||||
}
|
||||
|
||||
private disconnect(request: VSCodeDebugAdapterPackage.Request): void {
|
||||
// The client is about to disconnect so first we need to stop app worker
|
||||
this.appWorker.stop();
|
||||
|
||||
// Then we tell the extension to stop monitoring the logcat, and then we disconnect the debugging session
|
||||
if (this.mobilePlatformOptions.platform === "android") {
|
||||
this.remoteExtension.stopMonitoringLogcat()
|
||||
.catch(reason => Log.logError(`WARNING: Couldn't stop monitoring logcat: ${reason.message || reason}\n`))
|
||||
.finally(() => super.dispatchRequest(request));
|
||||
} else {
|
||||
super.dispatchRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
private requestSetup(args: any) {
|
||||
this.projectRootPath = getProjectRoot(args);
|
||||
this.remoteExtension = RemoteExtension.atProjectRootPath(this.projectRootPath);
|
||||
this.mobilePlatformOptions = {
|
||||
projectRoot: this.projectRootPath,
|
||||
platform: args.platform,
|
||||
};
|
||||
|
||||
// Start to send telemetry
|
||||
telemetryReporter.reassignTo(new ExtensionTelemetryReporter(
|
||||
appName, version, Telemetry.APPINSIGHTS_INSTRUMENTATIONKEY, this.projectRootPath));
|
||||
|
||||
Log.SetGlobalLogger(new NodeDebugAdapterLogger(debugAdapterPackage, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs logic needed to attach.
|
||||
* Attach should:
|
||||
* - Enable js debugging
|
||||
*/
|
||||
private attachRequest(request: VSCodeDebugAdapterPackage.Request, mobilePlatform?: GeneralMobilePlatform): Q.Promise<void> {
|
||||
return TelemetryHelper.generate("attach", (generator) => {
|
||||
return Q({})
|
||||
.then(() => {
|
||||
generator.step("mobilePlatform.enableJSDebuggingMode");
|
||||
if (mobilePlatform) {
|
||||
return mobilePlatform.enableJSDebuggingMode();
|
||||
} else {
|
||||
Log.logMessage("Debugger ready. Enable remote debugging in app.");
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
Log.logMessage("Starting debugger app worker.");
|
||||
// TODO: remove dependency on args.program - "program" property is technically
|
||||
// no more required in launch configuration and could be removed
|
||||
const workspaceRootPath = path.resolve(path.dirname(request.arguments.program), "..");
|
||||
const sourcesStoragePath = path.join(workspaceRootPath, ".vscode", ".react");
|
||||
|
||||
// If launch is invoked first time, appWorker is undefined, so create it here
|
||||
this.appWorker = new MultipleLifetimesAppWorker(this.packagerPort, sourcesStoragePath, {
|
||||
// Inject our custom debuggee worker
|
||||
sandboxedAppConstructor: (path: string, messageFunc: (message: any) => void) =>
|
||||
new ForkedAppWorker(this.packagerPort, path, messageFunc),
|
||||
}
|
||||
);
|
||||
|
||||
this.appWorker.on("connected", (port: number) => {
|
||||
// Don't mutate original request to avoid side effects
|
||||
let attachArguments = Object.assign({}, request.arguments, { port, restart: true, request: "attach" });
|
||||
let attachRequest = Object.assign({}, request, { command: "attach", arguments: attachArguments });
|
||||
|
||||
super.dispatchRequest(attachRequest);
|
||||
});
|
||||
|
||||
return this.appWorker.start();
|
||||
})
|
||||
.catch(error => this.bailOut(error.message));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs error to user and finishes the debugging process.
|
||||
*/
|
||||
private bailOut(message: string): void {
|
||||
Log.logError(`Could not debug. ${message}`);
|
||||
// use public terminateSession from Node2DebugAdapter
|
||||
// this.terminateSession(message);
|
||||
// this._session.sendEvent(new TerminatedEvent());
|
||||
// process.exit(1);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function makeAdapter(debugAdapterClass: typeof Node2DebugAdapterPackage.Node2DebugAdapter): typeof Node2DebugAdapterPackage.Node2DebugAdapter {
|
||||
return class extends debugAdapterClass {
|
||||
public doAttach(port: number, targetUrl?: string, address?: string, timeout?: number): Promise<void> {
|
||||
// HACK: Overwrite ChromeDebug's _attachMode to let Node2 adapter
|
||||
// to set up breakpoints on initial pause event
|
||||
this._attachMode = false;
|
||||
return super.doAttach(port, targetUrl, address, timeout);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses log cat arguments to a string
|
||||
*/
|
||||
function parseLogCatArguments(userProvidedLogCatArguments: any): string {
|
||||
return Array.isArray(userProvidedLogCatArguments)
|
||||
? userProvidedLogCatArguments.join(" ") // If it's an array, we join the arguments
|
||||
: userProvidedLogCatArguments; // If not, we leave it as-is
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to know if a value is either null or undefined
|
||||
*/
|
||||
function isNullOrUndefined(value: any): boolean {
|
||||
return typeof value === "undefined" || value === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses settings.json file for workspace root property
|
||||
*/
|
||||
function getProjectRoot(args: any): string {
|
||||
try {
|
||||
let vsCodeRoot = path.resolve(args.program, "../..");
|
||||
let settingsPath = path.resolve(vsCodeRoot, ".vscode/settings.json");
|
||||
let settingsContent = fs.readFileSync(settingsPath, "utf8");
|
||||
settingsContent = stripJsonComments(settingsContent);
|
||||
let parsedSettings = JSON.parse(settingsContent);
|
||||
let projectRootPath = parsedSettings["react-native-tools"].projectRoot;
|
||||
return path.resolve(vsCodeRoot, projectRootPath);
|
||||
} catch (e) {
|
||||
return path.resolve(args.program, "../..");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,11 @@ import {EntryPointHandler, ProcessType} from "../common/entryPointHandler";
|
|||
import {ErrorHelper} from "../common/error/errorHelper";
|
||||
import {InternalErrorCode} from "../common/error/internalErrorCode";
|
||||
import {NullTelemetryReporter, ReassignableTelemetryReporter} from "../common/telemetryReporters";
|
||||
import {NodeDebugWrapper} from "./nodeDebugWrapper";
|
||||
import { makeAdapter, makeSession } from "./nodeDebugWrapper";
|
||||
|
||||
const version = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "package.json"), "utf-8")).version;
|
||||
const telemetryReporter = new ReassignableTelemetryReporter(new NullTelemetryReporter());
|
||||
const appName = "react-native-debug-adapter";
|
||||
const extensionName = "react-native-debug-adapter";
|
||||
|
||||
function bailOut(reason: string): void {
|
||||
// Things have gone wrong in initialization: Report the error to telemetry and exit
|
||||
|
@ -22,7 +22,7 @@ function bailOut(reason: string): void {
|
|||
}
|
||||
|
||||
// Enable telemetry
|
||||
new EntryPointHandler(ProcessType.Debugger).runApp(appName, () => version,
|
||||
new EntryPointHandler(ProcessType.Debugger).runApp(extensionName, () => version,
|
||||
ErrorHelper.getInternalError(InternalErrorCode.DebuggingFailed), telemetryReporter, () => {
|
||||
|
||||
/**
|
||||
|
@ -31,14 +31,20 @@ new EntryPointHandler(ProcessType.Debugger).runApp(appName, () => version,
|
|||
* We look for node debug adapter on client's computer so we can jump of on top of that.
|
||||
*/
|
||||
let nodeDebugFolder: string;
|
||||
let vscodeDebugAdapterPackage: typeof VSCodeDebugAdapter;
|
||||
let VSCodeDebugAdapter: typeof VSCodeDebugAdapterPackage;
|
||||
let Node2DebugAdapter: typeof Node2DebugAdapterPackage.Node2DebugAdapter;
|
||||
let ChromeDebuggerPackage: typeof ChromeDebuggerCorePackage;
|
||||
|
||||
// nodeDebugLocation.json is dynamically generated on extension activation.
|
||||
// If it fails, we must not have been in a react native project
|
||||
try {
|
||||
/* tslint:disable:no-var-requires */
|
||||
nodeDebugFolder = require("./nodeDebugLocation.json").nodeDebugPath;
|
||||
vscodeDebugAdapterPackage = require(path.join(nodeDebugFolder, "node_modules", "vscode-debugadapter"));
|
||||
// FIXME: uncomment this before submitting
|
||||
// nodeDebugFolder = require("./nodeDebugLocation.json").nodeDebugPath;
|
||||
nodeDebugFolder = "/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/ms-vscode.node-debug2";
|
||||
VSCodeDebugAdapter = require(path.join(nodeDebugFolder, "node_modules/vscode-debugadapter"));
|
||||
ChromeDebuggerPackage = require(path.join(nodeDebugFolder, "node_modules/vscode-chrome-debug-core"));
|
||||
Node2DebugAdapter = require(path.join(nodeDebugFolder, "out/src/nodeDebugAdapter")).NodeDebugAdapter;
|
||||
/* tslint:enable:no-var-requires */
|
||||
} catch (e) {
|
||||
// Nothing we can do here: can't even communicate back because we don't know how to speak debug adapter
|
||||
|
@ -46,48 +52,28 @@ new EntryPointHandler(ProcessType.Debugger).runApp(appName, () => version,
|
|||
}
|
||||
|
||||
/**
|
||||
* We did find node debug adapter. Lets get the debugSession from it.
|
||||
* And add our customizations to the requests.
|
||||
* We did find chrome debugger package and node2 debug adapter. Lets create debug
|
||||
* session and adapter with our customizations.
|
||||
*/
|
||||
|
||||
// Temporarily dummy out the DebugSession.run function so we do not start the debug adapter until we are ready
|
||||
const originalDebugSessionRun = vscodeDebugAdapterPackage.DebugSession.run;
|
||||
vscodeDebugAdapterPackage.DebugSession.run = () => { };
|
||||
|
||||
let nodeDebug: { NodeDebugSession: typeof NodeDebugSession };
|
||||
let sourceMaps: { SourceMaps: typeof SourceMaps };
|
||||
let session: typeof ChromeDebuggerCorePackage.ChromeDebugSession;
|
||||
let adapter: typeof Node2DebugAdapterPackage.Node2DebugAdapter;
|
||||
|
||||
try {
|
||||
/* tslint:disable:no-var-requires */
|
||||
nodeDebug = require(path.join(nodeDebugFolder, "out", "node", "nodeDebug"));
|
||||
sourceMaps = require(path.join(nodeDebugFolder, "out", "node", "sourceMaps"));
|
||||
/* tslint:enable:no-var-requires */
|
||||
/* Create customised react-native debug adapter based on Node-debug2 adapter */
|
||||
adapter = makeAdapter(Node2DebugAdapter);
|
||||
// Create a debug session class based on ChromeDebugSession
|
||||
session = makeSession(ChromeDebuggerPackage.ChromeDebugSession,
|
||||
{ adapter, extensionName }, VSCodeDebugAdapter, telemetryReporter, extensionName, version);
|
||||
} catch (e) {
|
||||
// Unable to find nodeDebug, but we can make our own communication channel now
|
||||
const debugSession = new vscodeDebugAdapterPackage.DebugSession();
|
||||
// Note: this will not work in the context of debugging the debug adapter and communicating over a socket,
|
||||
// but in that case we have much better ways to investigate errors.
|
||||
debugSession.start(process.stdin, process.stdout);
|
||||
debugSession.sendEvent(new vscodeDebugAdapterPackage.OutputEvent("Unable to start debug adapter: " + e.toString(), "stderr"));
|
||||
debugSession.sendEvent(new vscodeDebugAdapterPackage.TerminatedEvent());
|
||||
|
||||
bailOut("cannotFindNodeDebugAdapter");
|
||||
}
|
||||
|
||||
vscodeDebugAdapterPackage.DebugSession.run = originalDebugSessionRun;
|
||||
|
||||
// Customize node adapter requests
|
||||
try {
|
||||
let nodeDebugWrapper = new NodeDebugWrapper(appName, version, telemetryReporter,
|
||||
vscodeDebugAdapterPackage, nodeDebug.NodeDebugSession, sourceMaps.SourceMaps);
|
||||
nodeDebugWrapper.customizeNodeAdapterRequests();
|
||||
} catch (e) {
|
||||
const debugSession = new vscodeDebugAdapterPackage.DebugSession();
|
||||
debugSession.sendEvent(new vscodeDebugAdapterPackage.OutputEvent("Unable to start debug adapter: " + e.toString(), "stderr"));
|
||||
debugSession.sendEvent(new vscodeDebugAdapterPackage.TerminatedEvent());
|
||||
// TODO: this is declared as abstract in vscode-chrome-debug-core.
|
||||
// Need to check whether that possible to instantiate it here
|
||||
const debugSession = new ChromeDebuggerPackage.ChromeDebugSession();
|
||||
debugSession.sendEvent(new VSCodeDebugAdapter.OutputEvent("Unable to start debug adapter: " + e.toString(), "stderr"));
|
||||
debugSession.sendEvent(new VSCodeDebugAdapter.TerminatedEvent());
|
||||
bailOut(e.toString());
|
||||
}
|
||||
|
||||
// Run the debug session for the node debug adapter with our modified requests
|
||||
vscodeDebugAdapterPackage.DebugSession.run(nodeDebug.NodeDebugSession);
|
||||
ChromeDebuggerPackage.ChromeDebugSession.run(session);
|
||||
});
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ export class ScriptImporter {
|
|||
this.sourceMapUtil = new SourceMapUtil();
|
||||
}
|
||||
|
||||
public downloadAppScript(scriptUrlString: string, debugAdapterPort: number): Q.Promise<DownloadedScript> {
|
||||
public downloadAppScript(scriptUrlString: string): Q.Promise<DownloadedScript> {
|
||||
const parsedScriptUrl = url.parse(scriptUrlString);
|
||||
const overriddenScriptUrlString = (parsedScriptUrl.hostname === "localhost") ? this.overridePackagerPort(scriptUrlString) : scriptUrlString;
|
||||
// We'll get the source code, and store it locally to have a better debugging experience
|
||||
|
@ -52,9 +52,6 @@ export class ScriptImporter {
|
|||
.then((scriptFilePath: string) => {
|
||||
Log.logInternalMessage(LogLevel.Info, `Script ${overriddenScriptUrlString} downloaded to ${scriptFilePath}`);
|
||||
return { contents: scriptBody, filepath: scriptFilePath };
|
||||
}).finally(() => {
|
||||
// Request that the debug adapter update breakpoints and sourcemaps now that we have written them
|
||||
return new Request().request(`http://localhost:${debugAdapterPort}/refreshBreakpoints`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -68,9 +68,7 @@ export function activate(context: vscode.ExtensionContext): void {
|
|||
warnWhenReactNativeVersionIsNotSupported();
|
||||
entryPointHandler.runFunction("debugger.setupLauncherStub",
|
||||
ErrorHelper.getInternalError(InternalErrorCode.DebuggerStubLauncherFailed), () =>
|
||||
setupReactNativeDebugger()
|
||||
.then(() =>
|
||||
setupAndDispose(new ReactDirManager(), context))
|
||||
setupAndDispose(new ReactDirManager(), context)
|
||||
.then(() =>
|
||||
setupAndDispose(new ExtensionServer(projectRootPath, globalPackager, packagerStatusIndicator, globalExponentHelper), context))
|
||||
.then(() => {}));
|
||||
|
@ -106,8 +104,7 @@ function configureLogLevel(): void {
|
|||
}
|
||||
|
||||
function configureNodeDebuggerLocation(): Q.Promise<void> {
|
||||
const nodeDebugExtension = vscode.extensions.getExtension("ms-vscode.node-debug") // We try to get the new version
|
||||
|| vscode.extensions.getExtension("andreweinand.node-debug"); // If it's not available, we try to get the old version
|
||||
const nodeDebugExtension = vscode.extensions.getExtension("ms-vscode.node-debug2");
|
||||
if (!nodeDebugExtension) {
|
||||
return Q.reject<void>(ErrorHelper.getInternalError(InternalErrorCode.CouldNotFindLocationOfNodeDebugger));
|
||||
}
|
||||
|
@ -154,33 +151,3 @@ function registerVSCodeCommand(
|
|||
`commandPalette.${commandName}`, error,
|
||||
commandHandler)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the debugger for the React Native project by dropping
|
||||
* the debugger stub into the workspace
|
||||
*/
|
||||
function setupReactNativeDebugger(): Q.Promise<void> {
|
||||
const launcherPath = require.resolve("../debugger/launcher");
|
||||
const pkg = require("../../package.json");
|
||||
const extensionVersionNumber = pkg.version;
|
||||
const extensionName = pkg.name;
|
||||
|
||||
let debuggerEntryCode =
|
||||
`// This file is automatically generated by ${extensionName}@${extensionVersionNumber}
|
||||
// Please do not modify it manually. All changes will be lost.
|
||||
try {
|
||||
var path = require("path");
|
||||
var Launcher = require(${JSON.stringify(launcherPath)}).Launcher;
|
||||
new Launcher("${workspaceRootPath.replace(/\\/g, "\\\\")}", "${projectRootPath.replace(/\\/g, "\\\\")}").launch();
|
||||
} catch (e) {
|
||||
throw new Error("Unable to launch application. Try deleting .vscode/launchReactNative.js and restarting vscode.");
|
||||
}`;
|
||||
const vsCodeFolder = path.join(workspaceRootPath, ".vscode");
|
||||
const debugStub = path.join(vsCodeFolder, "launchReactNative.js");
|
||||
|
||||
return fsUtil.ensureDirectory(vsCodeFolder)
|
||||
.then(() => fsUtil.ensureFileWithContents(debugStub, debuggerEntryCode))
|
||||
.catch((err: Error) => {
|
||||
vscode.window.showErrorMessage(err.message);
|
||||
});
|
||||
}
|
|
@ -18,7 +18,6 @@ suite("appWorker", function() {
|
|||
suite("SandboxedAppWorker", function() {
|
||||
const sourcesStoragePath = path.resolve(__dirname, "assets");
|
||||
const startScriptFileName = require.resolve(path.join(sourcesStoragePath, ScriptImporter.DEBUGGER_WORKER_FILE_BASENAME));
|
||||
const debugAdapterPort = 9090;
|
||||
|
||||
let sandboxedWorker: AppWorker.SandboxedAppWorker;
|
||||
let downloadAppScriptStub = sinon.stub();
|
||||
|
@ -34,7 +33,7 @@ suite("appWorker", function() {
|
|||
downloadAppScript: downloadAppScriptStub,
|
||||
};
|
||||
|
||||
sandboxedWorker = new AppWorker.SandboxedAppWorker(packagerPort, sourcesStoragePath, debugAdapterPort, postReplyFunction, {
|
||||
sandboxedWorker = new AppWorker.SandboxedAppWorker(packagerPort, sourcesStoragePath, postReplyFunction, {
|
||||
nodeFileSystem: nodeFileSystemMock,
|
||||
scriptImporter: scriptImporterMock,
|
||||
});
|
||||
|
@ -68,7 +67,7 @@ suite("appWorker", function() {
|
|||
|
||||
const testScriptContents = "postMessage('inImport')";
|
||||
const scriptImportDeferred = Q.defer<void>();
|
||||
downloadAppScriptStub.withArgs(scriptImportPath, debugAdapterPort).returns(scriptImportDeferred.promise.then(() => {
|
||||
downloadAppScriptStub.withArgs(scriptImportPath).returns(scriptImportDeferred.promise.then(() => {
|
||||
return {
|
||||
contents: testScriptContents,
|
||||
filepath: scriptImportPath,
|
||||
|
@ -117,7 +116,6 @@ suite("appWorker", function() {
|
|||
|
||||
suite("MultipleLifetimesAppWorker", function() {
|
||||
const sourcesStoragePath = path.resolve(__dirname, "assets");
|
||||
const debugAdapterPort = 9090;
|
||||
|
||||
let multipleLifetimesWorker: AppWorker.MultipleLifetimesAppWorker;
|
||||
let sandboxedAppWorkerStub: Sinon.SinonStub;
|
||||
|
@ -144,7 +142,7 @@ suite("appWorker", function() {
|
|||
packagerIsRunning = sinon.stub(Packager, "isPackagerRunning");
|
||||
packagerIsRunning.returns(Q.resolve(true));
|
||||
|
||||
multipleLifetimesWorker = new AppWorker.MultipleLifetimesAppWorker(packagerPort, sourcesStoragePath, debugAdapterPort, {
|
||||
multipleLifetimesWorker = new AppWorker.MultipleLifetimesAppWorker(packagerPort, sourcesStoragePath, {
|
||||
sandboxedAppConstructor: sandboxedAppConstructor,
|
||||
webSocketConstructor: webSocketConstructor,
|
||||
});
|
||||
|
@ -152,6 +150,7 @@ suite("appWorker", function() {
|
|||
|
||||
teardown(function() {
|
||||
// Reset everything
|
||||
multipleLifetimesWorker.stop();
|
||||
multipleLifetimesWorker = null;
|
||||
webSocket = null;
|
||||
sandboxedAppWorkerStub = null;
|
||||
|
|
Загрузка…
Ссылка в новой задаче