Skip to content
This repository has been archived by the owner on Feb 1, 2022. It is now read-only.

Commit

Permalink
feat: Launching the example works
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Krems committed Sep 3, 2016
1 parent 44c4c79 commit 985873c
Show file tree
Hide file tree
Showing 6 changed files with 391 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "groupon-es5"
"extends": "groupon-node6"
}
2 changes: 2 additions & 0 deletions cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/usr/bin/env node
require('./lib/cli.js');
8 changes: 8 additions & 0 deletions examples/myscript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* eslint no-restricted-syntax: 0, no-debugger: 0 */
'use strict';
const x = process.argv[2] || 'world';
setTimeout(() => {
debugger;
console.log(x);
}, 1000);
console.log('hello');
3 changes: 3 additions & 0 deletions lib/cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';
// ~= NativeModule.require('_debugger').start();
require('./node-inspect').start();
376 changes: 375 additions & 1 deletion lib/node-inspect.js
Original file line number Diff line number Diff line change
@@ -1 +1,375 @@
'use strict';
'use strict'; /* eslint no-underscore-dangle: 0 */
// Our equivalent of '_debugger' in node itthis
const assert = require('assert');
const repl = require('repl');
const spawn = require('child_process').spawn;
const util = require('util');

exports.port = process.debugPort;

class Client {
once() {}
destroy() {}
on() {}
connect() {}
}

// This class is the repl-enabled debugger interface which is invoked on
// "node-inspect"
class Inspector {
constructor(stdin, stdout, args) {
this.stdin = stdin;
this.stdout = stdout;
this.args = args;

// Two eval modes are available: controlEval and debugEval
// But controlEval is used by default
const opts = {
prompt: 'debug> ',
input: this.stdin,
output: this.stdout,
eval: (code, ctx, file, cb) => this.controlEval(code, ctx, file, cb),
useGlobal: false,
ignoreUndefined: true,
};
if (parseInt(process.env.NODE_NO_READLINE, 10)) {
opts.terminal = false;
} else if (parseInt(process.env.NODE_FORCE_READLINE, 10)) {
opts.terminal = true;

// Emulate Ctrl+C if we're emulating terminal
if (!this.stdout.isTTY) {
process.on('SIGINT', () => {
this.repl.rli.emit('SIGINT');
});
}
}
if (parseInt(process.env.NODE_DISABLE_COLORS, 10)) {
opts.useColors = false;
}

this.repl = repl.start(opts);

// Do not print useless warning
repl._builtinLibs.splice(repl._builtinLibs.indexOf('repl'), 1);

// Kill child process when main process dies
this.repl.on('exit', () => {
process.exit(0);
});

// Handle all possible exits
process.on('exit', () => this.killChild());
process.once('SIGTERM', process.exit.bind(process, 0));
process.once('SIGHUP', process.exit.bind(process, 0));

// const proto = Inspector.prototype;
// const ignored = ['pause', 'resume', 'exitRepl', 'handleBreak',
// 'requireConnection', 'killChild', 'trySpawn',
// 'controlEval', 'debugEval', 'print', 'childPrint',
// 'clearline'];
// const shortcut = {
// run: 'r',
// cont: 'c',
// next: 'n',
// step: 's',
// out: 'o',
// backtrace: 'bt',
// setBreakpoint: 'sb',
// clearBreakpoint: 'cb',
// pause_: 'pause',
// };

// function defineProperty(key, protoKey) {
// // Check arity
// const fn = proto[protoKey].bind(this);

// if (proto[protoKey].length === 0) {
// Object.defineProperty(this.repl.context, key, {
// get: fn,
// enumerable: true,
// configurable: false,
// });
// } else {
// this.repl.context[key] = fn;
// }
// }

// // Copy all prototype methods in repl context
// // Setup them as getters if possible
// for (var i in proto) {
// if (Object.prototype.hasOwnProperty.call(proto, i) &&
// ignored.indexOf(i) === -1) {
// defineProperty(i, i);
// if (shortcut[i]) defineProperty(shortcut[i], i);
// }
// }

this.killed = false;
this.waiting = null;
this.paused = 0;
this.context = this.repl.context;
this.history = { debug: [], control: [] };
this.breakpoints = [];
this._watchers = [];

// Run script automatically
this.pause();

setImmediate(() => { this.run(); });
}

// Clear current line
clearline() {
if (this.stdout.isTTY) {
this.stdout.cursorTo(0);
this.stdout.clearLine(1);
} else {
this.stdout.write('\b');
}
}

// Print text to output stream
print(text, oneline) {
if (this.killed) return;
this.clearline();

this.stdout.write(typeof text === 'string' ? text : util.inspect(text));

if (oneline !== true) {
this.stdout.write('\n');
}
}

// Format and print text from child process
childPrint(text) {
this.print(
text.toString()
.split(/\r\n|\r|\n/g)
.filter(chunk => !!chunk)
.map(chunk => `< ${chunk}`)
.join('\n')
);
this.repl.displayPrompt(true);
}

// Errors formatting
error(text) {
this.print(text);
this.resume();
}


// Stream control


pause() {
if (this.killed || this.paused++ > 0) return this;
this.repl.rli.pause();
this.stdin.pause();
return this;
}

resume(silent) {
if (this.killed || this.paused === 0 || --this.paused !== 0) return this;
this.repl.rli.resume();
if (silent !== true) {
this.repl.displayPrompt();
}
this.stdin.resume();

if (this.waiting) {
this.waiting();
this.waiting = null;
}
return this;
}


// Commands


// // Print help message
// help() {
// this.print(helpMessage);
// }


// Run script
run(callback) {
if (this.child) {
this.error('App is already running... Try `restart` instead');
if (callback) callback(true);
} else {
this.trySpawn(callback);
}
}


// Quit
quit() {
this.killChild();
process.exit(0);
}

// Kills child process
killChild() {
if (this.child) {
this.child.kill();
this.child = null;
}

if (this.client) {
// Save breakpoints
this.breakpoints = this.client.breakpoints;

this.client.destroy();
this.client = null;
}
}

// Spawns child process (and restores breakpoints)
trySpawn(cb) {
const breakpoints = this.breakpoints || [];
let port = exports.port;
let host = '127.0.0.1';
let childArgs = this.args;

this.killChild();
assert(!this.child);

let isRemote = false;
if (this.args.length === 2) {
const match = this.args[1].match(/^([^:]+):(\d+)$/);

if (match) {
// Connecting to remote debugger
// `node debug localhost:5858`
host = match[1];
port = parseInt(match[2], 10);
isRemote = true;
}
} else if (this.args.length === 3) {
// `node debug -p pid`
if (this.args[1] === '-p' && /^\d+$/.test(this.args[2])) {
const pid = parseInt(this.args[2], 10);
try {
process._debugProcess(pid);
} catch (e) {
if (e.code === 'ESRCH') {
console.error(`Target process: ${pid} doesn't exist.`);
process.exit(1);
}
throw e;
}
isRemote = true;
} else {
const match = this.args[1].match(/^--port=(\d+)$/);
if (match) {
// Start debugger on custom port
// `node debug --port=5858 app.js`
port = parseInt(match[1], 10);
childArgs = [`--inspect=${port}`, '--debug-brk'].concat(this.args.slice(2));
}
}
}

if (!isRemote) {
// pipe stream into debugger
this.child = spawn(process.execPath, childArgs);

this.child.stdout.on('data', text => this.childPrint(text));
this.child.stderr.on('data', text => this.childPrint(text));
}

this.pause();

const client = this.client = new Client();
let connectionAttempts = 0;

client.once('ready', () => {
this.stdout.write(' ok\n');

// Restore breakpoints
breakpoints.forEach(bp => {
this.print(`Restoring breakpoint ${bp.scriptReq}: ${bp.line}`);
this.setBreakpoint(bp.scriptReq, bp.line, bp.condition, true);
});

client.on('close', () => {
this.pause();
this.print('program terminated');
this.resume();
this.client = null;
this.killChild();
});

if (cb) cb();
this.resume();
});

client.on('unhandledResponse', res => {
this.pause();
this.print(`\nunhandled res:${JSON.stringify(res)}`);
this.resume();
});

client.on('break', res => {
this.handleBreak(res.body);
});

client.on('exception', res => {
this.handleBreak(res.body);
});

const attemptConnect = () => {
++connectionAttempts;
this.stdout.write('.');
client.connect(port, host);
};

client.on('error', () => {
// If it's failed to connect 10 times then print failed message
if (connectionAttempts >= 10) {
console.error(' failed to connect, please retry');
process.exit(1);
}
setTimeout(attemptConnect, 500);
});

if (isRemote) {
this.print(`connecting to ${host}:${port} ..`, true);
attemptConnect();
} else {
this.child.stderr.once('data', () => {
this.print(`connecting to ${host}:${port} ..`, true);
setImmediate(attemptConnect);
});
}
}
}

exports.start = function start(argv = process.argv.slice(2),
stdin = process.stdin,
stdout = process.stdout) {
if (argv.length < 1) {
console.error('Usage: node debug script.js');
console.error(' node debug <host>:<port>');
console.error(' node debug -p <pid>');
process.exit(1);
}

const args = [`--inspect=${exports.port}`, '--debug-brk'].concat(argv);
const inspector = new Inspector(stdin, stdout, args);

stdin.resume();

process.on('uncaughtException', e => {
console.error('There was an internal error in node-inspect. ' +
'Please report this bug.');
console.error(e.message);
console.error(e.stack);
if (inspector.child) inspector.child.kill();
process.exit(1);
});
};
Loading

0 comments on commit 985873c

Please sign in to comment.