node-debug.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. #!/usr/bin/env node
  2. var fork = require('child_process').fork;
  3. var fs = require('fs');
  4. var path = require('path');
  5. var util = require('util');
  6. var open = require('opener');
  7. var yargs = require('yargs');
  8. var whichSync = require('which').sync;
  9. var inspector = require('..');
  10. var WIN_CMD_LINK_MATCHER = /node "%~dp0\\(.*?)"/;
  11. var argvOptions = {
  12. 'debug-brk': {
  13. alias: 'b',
  14. default: true,
  15. description: 'Break on the first line (`node --debug-brk`)'
  16. },
  17. 'web-port': {
  18. alias: ['p', 'port'],
  19. type: 'number',
  20. description: 'Node Inspector port (`node-inspector --web-port={port}`)'
  21. },
  22. 'debug-port': {
  23. alias: 'd',
  24. type: 'number',
  25. description: 'Node/V8 debugger port (`node --debug={port}`)'
  26. },
  27. nodejs: {
  28. type: 'string',
  29. description: 'Pass NodeJS options to debugged process (`node --option={value}`)\n' +
  30. 'Usage example: node-debug --nodejs --harmony --nodejs --random_seed=2 app'
  31. },
  32. cli: {
  33. alias: 'c',
  34. type: 'boolean',
  35. description: 'CLI mode, do not open browser.'
  36. },
  37. version: {
  38. alias: 'v',
  39. type: 'boolean',
  40. description: 'Print Node Inspector\'s version.'
  41. },
  42. help: {
  43. alias: 'h',
  44. type: 'boolean',
  45. description: 'Show this help.'
  46. }
  47. };
  48. var argvParser = createYargs();
  49. module.exports = main;
  50. module.exports.parseArgs = parseArgs;
  51. if (require.main == module)
  52. main();
  53. //-- Implementation --
  54. var config;
  55. /**
  56. * By default:
  57. *
  58. * 1. Runs node-inspector.
  59. * 2. Runs the supplied script in debug mode
  60. * 3. Opens the user's browser, pointing it at the inspector.
  61. *
  62. * NOTE: Finishes with a call to process.exit() when running without arguments
  63. * or when an error occured.
  64. */
  65. function main() {
  66. config = parseArgs(process.argv);
  67. if (config.options.help) {
  68. argvParser.showHelp(console.log);
  69. console.log('The [script] argument is resolved relative to the current working\n' +
  70. 'directory. If no such file exists, then env.PATH is searched.\n');
  71. console.log('The default mode is to break on the first line of the script, to run\n' +
  72. 'immediately on start use `--no-debug-brk` or press the Resume button.\n');
  73. console.log('When there is no script specified, the module in the current working\n' +
  74. 'directory is loaded in the REPL session as `m`. This allows you to call\n' +
  75. 'and debug arbitrary functions exported by the current module.\n');
  76. process.exit();
  77. }
  78. if (config.options.version) {
  79. console.log('v' + require('../package.json').version);
  80. process.exit();
  81. }
  82. startInspector(function(err) {
  83. if (err) {
  84. console.error(formatNodeInspectorError(err));
  85. process.exit(1);
  86. }
  87. startDebuggedProcess(function(err) {
  88. if (err) {
  89. console.error(
  90. 'Cannot start %s: %s',
  91. config.subproc.script,
  92. err.message || err
  93. );
  94. process.exit(2);
  95. }
  96. openBrowserAndPrintInfo();
  97. });
  98. });
  99. }
  100. function parseArgs(argv) {
  101. argv = argv.slice(2);
  102. //Preparse --nodejs options
  103. var nodejsArgs = [];
  104. var nodejsIndex = argv.indexOf('--nodejs');
  105. while (nodejsIndex !== -1) {
  106. var nodejsArg = argv.splice(nodejsIndex, 2)[1];
  107. if (nodejsArg !== undefined) {
  108. nodejsArgs.push(nodejsArg);
  109. }
  110. nodejsIndex = argv.indexOf('--nodejs');
  111. }
  112. var options = argvParser.parse(argv);
  113. var script = options._[0];
  114. var printScript = true;
  115. var subprocArgs;
  116. if (script) {
  117. // We want to pass along subarguments, but re-parse our arguments.
  118. subprocArgs = argv.splice(argv.indexOf(script) + 1);
  119. } else {
  120. script = require.resolve('./run-repl');
  121. subprocArgs = [];
  122. printScript = false;
  123. process.env.CMD = process.env.CMD || process.argv[1];
  124. }
  125. options = argvParser.parse(argv);
  126. var subprocPort = options['debug-port'] || 5858;
  127. var subprocExecArgs = ['--debug=' + subprocPort].concat(nodejsArgs);
  128. if (options['debug-brk']) {
  129. subprocExecArgs.push('--debug-brk');
  130. }
  131. var inspectorPort = options['web-port'] || 8080;
  132. var inspectorArgs = extractPassThroughArgs(options, argvOptions)
  133. .concat(['--web-port=' + inspectorPort]);
  134. return {
  135. printScript: printScript,
  136. options: options,
  137. subproc: {
  138. script: script,
  139. args: subprocArgs,
  140. execArgs: subprocExecArgs,
  141. debugPort: subprocPort
  142. },
  143. inspector: {
  144. port: inspectorPort,
  145. args: inspectorArgs
  146. }
  147. };
  148. }
  149. function createYargs() {
  150. var y = yargs
  151. .options(argvOptions)
  152. .usage('Usage:\n' +
  153. ' $0 [node-inspector-options] [options] script [script-arguments]');
  154. y.$0 = getCmd();
  155. return y;
  156. }
  157. function getCmd() {
  158. return process.env.CMD || path.basename(process.argv[1]);
  159. }
  160. function extractPassThroughArgs(options, argvOptions) {
  161. var result = [];
  162. var optionsToSkip = { _: true, $0: true };
  163. // Skip options handled by node-debug
  164. Object.keys(argvOptions).forEach(function(key) {
  165. optionsToSkip[key] = true;
  166. var alias = argvOptions[key].alias;
  167. if (Array.isArray(alias)) {
  168. alias.forEach(function(opt) { optionsToSkip[opt] = true; });
  169. } else if (alias) {
  170. optionsToSkip[alias] = true;
  171. }
  172. });
  173. // Filter options not handled by node-debug
  174. Object.keys(options).forEach(function(key) {
  175. //Filter options handled by node-debug
  176. if (optionsToSkip[key]) return;
  177. //Filter camelKey options created by yargs
  178. if (/[A-Z]/.test(key)) return;
  179. var value = options[key];
  180. if (value === undefined) return;
  181. if (value === true) {
  182. result.push('--' + key);
  183. } else if (value === false) {
  184. result.push('--no-' + key);
  185. } else {
  186. result.push('--' + key);
  187. result.push(value);
  188. }
  189. });
  190. return result;
  191. }
  192. function startInspector(callback) {
  193. var inspectorProcess = fork(
  194. require.resolve('./inspector'),
  195. config.inspector.args,
  196. { silent: true }
  197. );
  198. inspectorProcess.once('message', function(msg) {
  199. switch (msg.event) {
  200. case 'SERVER.LISTENING':
  201. return callback(null, msg.address);
  202. case 'SERVER.ERROR':
  203. return callback(msg.error);
  204. default:
  205. console.warn('Unknown Node Inspector event: %s', msg.event);
  206. return callback(
  207. null,
  208. {
  209. address: 'localhost',
  210. port: config.inspector.port
  211. }
  212. );
  213. }
  214. });
  215. process.on('exit', function() {
  216. inspectorProcess.kill();
  217. });
  218. }
  219. function formatNodeInspectorError(err) {
  220. var reason = err.message || err.code || err;
  221. if (err.code === 'EADDRINUSE') {
  222. reason += '\nThere is another process already listening at 0.0.0.0:' +
  223. config.inspector.port + '.\n' +
  224. 'Run `' + getCmd() + ' -p {port}` to use a different port.';
  225. }
  226. return util.format('Cannot start Node Inspector:', reason);
  227. }
  228. function startDebuggedProcess(callback) {
  229. var script = path.resolve(process.cwd(), config.subproc.script);
  230. if (!fs.existsSync(script)) {
  231. try {
  232. script = whichSync(config.subproc.script);
  233. script = checkWinCmdFiles(script);
  234. } catch (err) {
  235. return callback(err);
  236. }
  237. }
  238. var debuggedProcess = fork(
  239. script,
  240. config.subproc.args,
  241. {
  242. execArgv: config.subproc.execArgs
  243. }
  244. );
  245. debuggedProcess.on('exit', function() { process.exit(); });
  246. callback();
  247. }
  248. function checkWinCmdFiles(script) {
  249. if (process.platform == 'win32' && path.extname(script).toLowerCase() == '.cmd') {
  250. var cmdContent = '' + fs.readFileSync(script);
  251. var link = (WIN_CMD_LINK_MATCHER.exec(cmdContent) || [])[1];
  252. if (link) script = path.resolve(path.dirname(script), link);
  253. }
  254. return script;
  255. }
  256. function openBrowserAndPrintInfo() {
  257. var url = inspector.buildInspectorUrl(
  258. 'localhost',
  259. config.inspector.port,
  260. config.subproc.debugPort
  261. );
  262. if (!config.options.cli) {
  263. open(url);
  264. }
  265. console.log('Node Inspector is now available from %s', url);
  266. if (config.printScript)
  267. console.log('Debugging `%s`\n', config.subproc.script);
  268. }