Skip to content

Commit

Permalink
Implements options to specify port. Improves clipboard error handling.
Browse files Browse the repository at this point in the history
  • Loading branch information
hediet committed Mar 22, 2019
1 parent d71a2d5 commit df0d9b4
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 43 deletions.
2 changes: 1 addition & 1 deletion demo/main.js
Expand Up @@ -2,4 +2,4 @@ console.log("Started");
const now = new Date();
require("C:\\Users\\henni\\AppData\\Local\\Yarn\\Data\\global\\node_modules\\easy-attach\\")();
const dayOfWeek = now.getDayOfWeek();
console.log("Day of week:", dayOfWeek);
console.log("Day of week:", dayOfWeek);
4 changes: 4 additions & 0 deletions entries/index.d.ts
@@ -0,0 +1,4 @@
import { EasyAttachArgs } from "../dist/lib";

declare const launchDebugger: (args: EasyAttachArgs) => void;
export = launchDebugger;
1 change: 0 additions & 1 deletion entries/index.js
Expand Up @@ -7,6 +7,5 @@ function randomInt(min, max) {
const randomIndex = randomInt(0, entries.length - 1);

// choose a random quote.
/** @type {(arg: import("../src/lib").EasyAttachArgs) => void} */
const entry = entries[randomIndex];
module.exports = entry;
3 changes: 2 additions & 1 deletion package.json
@@ -1,12 +1,13 @@
{
"name": "easy-attach",
"version": "0.15.3",
"version": "0.16.0",
"author": {
"name": "Henning Dieterichs"
},
"description": "A helper tool that makes launching the debugger to step through obscure node-js scripts (e.g. webpack configurations) extremely easy.",
"homepage": "https://github.com/hediet/easy-attach",
"main": "./entries/index.js",
"types": "./entries/index.d.ts",
"license": "MIT",
"bin": {
"easy-attach": "./dist/cli.entry.js"
Expand Down
17 changes: 10 additions & 7 deletions src/background-worker/entry.ts
Expand Up @@ -4,12 +4,15 @@ import { AttachContext, Result } from "./attachContext";
import { EventSource } from "@hediet/std/events";
import { Disposable, dispose } from "@hediet/std/disposable";
import { launchProxyServer } from "../debugger-proxy";
import { BackgroundWorkerArgs } from ".";

const args = process.argv.slice(2);
const debuggerPort = parseInt(args[0]);
const label = args.length > 1 ? args[1] : undefined;
const argsObj = JSON.parse(process.argv[2]) as BackgroundWorkerArgs;

launchProxyServer(debuggerPort).then(data => {
launchProxyServer({
debugPort: argsObj.debugPort,
eagerExit: argsObj.eagerExitDebugProxy,
debugProxyPortConfig: argsObj.debugProxyPortConfig,
}).then(data => {
launch(data.port, data.onClientConnected, data.signalExit);
});

Expand All @@ -30,13 +33,13 @@ async function launch(
});

const context: AttachContext = {
debuggerPort,
debuggerPort: argsObj.debugPort,
proxyPort,
disposables,
exit,
label,
label: argsObj.label,
log: (message: string) => {
if (process.env.DEBUG_EASY_ATTACH) {
if (process.env.DEBUG_EASY_ATTACH || argsObj.log) {
console.log(message);
}
},
Expand Down
22 changes: 15 additions & 7 deletions src/background-worker/index.ts
@@ -1,14 +1,22 @@
import { join } from "path";
import { spawnSync } from "child_process";
import { PortConfig } from "../lib";

export interface BackgroundWorkerArgs {
debugPort: number;
label: string | undefined;
eagerExitDebugProxy: boolean;
log: boolean;
debugProxyPortConfig: PortConfig;
}

export function launchAndWaitForBackgroundProcessSync(
debugPort: number,
label: string | undefined
args: BackgroundWorkerArgs
) {
const entry = join(__dirname, "./entry.js");
spawnSync(
"node",
[`${entry}`, debugPort.toString(), label ? JSON.stringify(label) : ""],
{ stdio: "inherit", shell: true, windowsHide: true }
);
spawnSync("node", [`${entry}`, JSON.stringify(args)], {
stdio: "inherit",
shell: false,
windowsHide: true,
});
}
11 changes: 8 additions & 3 deletions src/cli.entry.ts
Expand Up @@ -12,7 +12,12 @@ console.log(`Easy Attach Version ${pkg.version}.`);
console.log();
console.log("Use this code to trigger a breakpoint:");
console.log(chalk.blue(codeToTriggerDebugger));
console.log("(Pasted into your clipboard)");
console.log();

clipboardy.writeSync(codeToTriggerDebugger);
try {
clipboardy.writeSync(codeToTriggerDebugger);
console.log("(Copied to clipboard)");
} catch (e) {
console.error(chalk.red(`Could not copy to clipboard: ${e.toString()}`));
}

console.log();
39 changes: 30 additions & 9 deletions src/debugger-proxy/entry.ts
Expand Up @@ -2,12 +2,12 @@ import { AddressInfo } from "net";
import { debuggerProxyContract } from "./contract";
import httpProxy = require("http-proxy");
import WsParser = require("simples/lib/parsers/ws");
import { TypedChannel } from "@hediet/typed-json-rpc";
import { NodeJsMessageStream } from "@hediet/typed-json-rpc-streams";
import { ResettableTimeout } from "@hediet/std/timer";
import { DebuggerProxyArgs } from ".";
import getPort from "get-port";

const args = process.argv.slice(2);
const debuggerPort = parseInt(args[0]);
const argsObj = JSON.parse(process.argv[2]) as DebuggerProxyArgs;

let clientConnected = false;
const timeout = new ResettableTimeout(5000);
Expand All @@ -28,20 +28,30 @@ const { client } = debuggerProxyContract.registerServerToStream(
}
);

function handleClientConnected() {
if (!clientConnected) {
clientConnected = true;
client.clientConnected({});
}
}

const server = httpProxy.createServer({
target: `http://localhost:${debuggerPort}`,
target: `http://localhost:${argsObj.debugPort}`,
ws: true,
});

server.on("proxyReqWs", (proxyReq, req, socket, options) => {
if (argsObj.eagerExit) {
handleClientConnected();
}

const parser = new WsParser(0, false);
socket.pipe(parser);

parser.on("frame", (frame: { data: any }) => {
const content = frame.data.toString("utf8") as string;
if (content.indexOf("Runtime.runIfWaitingForDebugger") !== -1) {
clientConnected = true;
client.clientConnected({});
handleClientConnected();
}
});

Expand All @@ -62,7 +72,18 @@ server._server.on("connection", socket => {
});
*/

server.listen(0);
async function run() {
let port: number;
if (argsObj.debugProxyPortConfig === "random") {
port = 0;
} else {
port = await getPort({ port: argsObj.debugProxyPortConfig });
}

server.listen(port);

const info = (server as any)._server.address() as AddressInfo;
client.serverStarted({ port: info.port });
}

const info = (server as any)._server.address() as AddressInfo;
client.serverStarted({ port: info.port });
run();
12 changes: 9 additions & 3 deletions src/debugger-proxy/index.ts
@@ -1,14 +1,20 @@
import { spawn } from "child_process";
import { join } from "path";
import { EventEmitter, EventSource } from "@hediet/std/events";
import { TypedChannel } from "@hediet/typed-json-rpc";
import { NodeJsMessageStream } from "@hediet/typed-json-rpc-streams";
import { debuggerProxyContract } from "./contract";
import { startInterval } from "@hediet/std/timer";
import { Disposable } from "@hediet/std/disposable";
import { PortConfig } from "../lib";

export interface DebuggerProxyArgs {
debugPort: number;
debugProxyPortConfig: PortConfig;
eagerExit: boolean;
}

export function launchProxyServer(
port: number
args: DebuggerProxyArgs
): Promise<{
port: number;
onClientConnected: EventSource;
Expand All @@ -17,7 +23,7 @@ export function launchProxyServer(
const onClientConnected = new EventEmitter();
return new Promise(resolve => {
const entry = join(__dirname, "./entry.js");
const proc = spawn("node", [entry, port.toString()], {
const proc = spawn("node", [entry, JSON.stringify(args)], {
detached: true,
shell: false,
windowsHide: true,
Expand Down
96 changes: 85 additions & 11 deletions src/lib.ts
@@ -1,9 +1,36 @@
import child_process = require("child_process");
import { launchAndWaitForBackgroundProcessSync } from "./background-worker";

export type PortConfig = "random" | number | number[];
export type DebugPortConfig = PortConfig | "preconfigured";

export interface EasyAttachArgs {
label: string;
continue: boolean;
/**
* Sets a label for the debug target.
*/
label?: string;
/**
* If enabled, it does not break after attaching the debugger.
*/
continue?: boolean;
/**
* Specifies the port to use for the debug port.
* Use `preconfigured` when the debugger was already launched.
*/
debugPort?: DebugPortConfig;
/**
* Specifies the port to use for the debug proxy.
* This is usefull if you want to forward this port.
*/
debugProxyPort?: PortConfig;
/**
* Use this option when the debug proxy does not recognize connection attempts.
*/
eagerExitDebugProxy?: boolean;
/**
* Print logs from background worker.
*/
logBackgroundWorker?: boolean;
}

let first = true;
Expand All @@ -13,9 +40,35 @@ module.exports.debugProcessAndWait = function(args?: EasyAttachArgs): boolean {
}
first = false;

const label = args ? args.label : undefined;
const { debugPort } = initializeDebugPort();
launchAndWaitForBackgroundProcessSync(debugPort, label);
let label = undefined;
let debugPortConfig: DebugPortConfig = "random";
let debugProxyPortConfig: PortConfig = "random";
let eagerExitDebugProxy = false;
let log = false;

if (args) {
label = args.label;
debugPortConfig = args.debugPort || "random";
if (args.eagerExitDebugProxy !== undefined) {
eagerExitDebugProxy = args.eagerExitDebugProxy;
}
if (args.logBackgroundWorker !== undefined) {
log = args.logBackgroundWorker;
}

if (args.debugProxyPort) {
debugProxyPortConfig = args.debugProxyPort;
}
}

const { debugPort } = initializeDebugPort(debugPortConfig);
launchAndWaitForBackgroundProcessSync({
debugPort,
label,
log,
eagerExitDebugProxy,
debugProxyPortConfig,
});

// Wait a bit so that the dev tools can connect properly.
waitSomeCycles();
Expand All @@ -29,24 +82,45 @@ module.exports.debugProcessAndWait = function(args?: EasyAttachArgs): boolean {

let debugPort: number | undefined = undefined;

function initializeDebugPort(): { debugPort: number } {
function initializeDebugPort(
portConfig: DebugPortConfig
): { debugPort: number } {
// use a random port for debugPort to prevent port clashes.
if (!debugPort) {
debugPort = getRandomPortSync();
process.debugPort = debugPort;
if (portConfig === "preconfigured") {
debugPort = process.debugPort;
} else {
if (portConfig === "random") {
debugPort = getRandomPortSync();
} else if (typeof portConfig === "number") {
debugPort = portConfig;
} else {
debugPort = getRandomPortSync(portConfig);
}
process.debugPort = debugPort;
}

(process as any)._debugProcess(process.pid);
}
return { debugPort };
}

function getRandomPortSync(): number {
function getRandomPortSync(allowedPorts?: number[]): number {
let options = "";
if (allowedPorts !== undefined) {
options = `{ port: [${allowedPorts
.map(p => (+p).toString())
.join(",")}] }`;
}
// we must use execSync as get-port is async.
const portStr = child_process.execSync(
`node -e "require('get-port')().then(p => console.log(p))"`,
`node -e "require('get-port')(${options}).then(p => console.log(p))"`,
// Set cwd so that node_modules can be found.
{ cwd: __dirname, encoding: "utf8" }
);
return parseInt(portStr);
const port = parseInt(portStr);
// It could be that `port` is not from `allowedPorts` as they are only preferences.
return port;
}

function waitSomeCycles() {
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Expand Up @@ -7,6 +7,7 @@
"skipLibCheck": true,
"rootDir": "./src",
"resolveJsonModule": true,
"declaration": true,
"newLine": "LF"
},
"include": ["./src/**/*"]
Expand Down

0 comments on commit df0d9b4

Please sign in to comment.