You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
venv/PyCharm 2025.2.1.1/plugins/javascript-debugger/nodeDebugInitializer/debugConnector.js

194 lines
6.3 KiB

const debugConnectorUtil = require('./debugConnectorUtil');
try {
if (debugConnectorUtil.isVerboseLoggingEnabled()) {
log('Starting execArgv=' + JSON.stringify(process.execArgv) + ', argv=' + JSON.stringify(process.argv));
}
const reason = findReasonToSkipAttachingDebugger();
if (reason != null) {
if (debugConnectorUtil.isVerboseLoggingEnabled()) {
log('Attaching debugger skipped: ' + reason);
}
return;
}
const inspector = require('inspector');
const asyncInspectorOpenSupported = isAsyncInspectorOpenSupported();
const debugPort = findAvailablePort(inspector, asyncInspectorOpenSupported);
if (debugConnectorUtil.getGatewayHostPort() != null) {
debugConnectorUtil.forwardDebugConnectionAndWait(debugPort);
return;
}
const {execFile, execFileSync} = require('child_process');
const launchPortPublisher = asyncInspectorOpenSupported ? execFileSync : execFile;
const interpreter = process.env['JB_INTERPRETER'] || process.execPath;
const publisherEnv = Object.assign({}, process.env, {
NODE_OPTIONS: ''
});
if (debugConnectorUtil.isVerboseLoggingEnabled()) {
log('interpreter to publish debug port: ' + interpreter
+ ', asyncInspectorOpenSupported: ' + asyncInspectorOpenSupported
+ ', process.versions.node: ' + (process.versions || {}).node);
}
launchPortPublisher(interpreter, [require.resolve('./debugConnectorPortPublisher.js'), debugPort.toString()], {
env: publisherEnv,
stdio: 'inherit',
windowsHide: true
});
if (!asyncInspectorOpenSupported) {
inspector.open(debugPort, getBindHost(), true);
}
}
catch (e) {
console.error(formatMessage('Attaching debugger skipped (unhandled exception)'), e);
}
function findReasonToSkipAttachingDebugger() {
if (hasInspectArg()) {
return '--inspect* passed';
}
if (isElectronRendererProcess()) {
return 'electron process detected';
}
const helperProcessInfo = getMatchedHelperProcess();
if (helperProcessInfo != null) {
return 'matched as helper process ' + JSON.stringify(helperProcessInfo);
}
try {
if (!require('worker_threads').isMainThread) {
// will be attached using WIP NodeWorker domain
return 'worker thread detected';
}
}
catch (ignored) {
}
return null;
}
function hasInspectArg() {
return process.execArgv.some(
arg => arg === '--inspect' || arg === '--inspect-brk' || arg.startsWith('--inspect-brk=') || arg.startsWith('--inspect=')
);
}
function isElectronRendererProcess() {
return process.type && process.type === 'renderer' || process.argv.indexOf('--type=renderer') >= 0;
}
/**
* A helper process is a Node.js process that is known not to run application code,
* so it's safe not to attach a debugger to it.
* Skipping attaching debugger to a helper process would produce less debugger related output in the console.
*
* @returns {Array<String>} a helper process info matching the current process; null if none matched
*/
function getMatchedHelperProcess() {
const INTERPRETER_DIR_MACRO = '[interpreter dir]';
const helperProcessInfoList = [
['/node_modules/npm/bin/npm-cli.js', 'prefix', '-g'], // 'npm prefix -g' is run by npm.cmd on Windows
['/node_modules/update-notifier/check.js'], // check for npm/yarn available updates
// npm/Yarn script (e.g. 'npm run start') spawns the process hierarchy,
// where the root Node.js process doesn't run any application code
['/node_modules/npm/bin/npm-cli.js', 'run'],
[INTERPRETER_DIR_MACRO + '/npm', 'run'], // ~/.nvm/versions/node/v18.12.1/bin/npm ('npm' and 'node' are the same folder)
['/Yarn/bin/yarn.js', 'run'],
['/usr/local/bin/npm', 'run']
];
if (process.env.JETBRAINS_NODE_DEBUGGER_ATTACH_TO_HELPERS) {
return null;
}
const argv = process.argv;
return helperProcessInfoList.find((helperProcessInfo) => {
return isHelperInfoMatched(helperProcessInfo, argv);
});
/**
* @param {Array<String>} helperProcessInfo
* @param {Array<String>} argv
* @returns {boolean}
*/
function isHelperInfoMatched(helperProcessInfo, argv) {
if (argv.length <= helperProcessInfo.length || helperProcessInfo.length === 0) {
return false;
}
return helperProcessInfo.every((value, index) => {
if (index === 0) {
return matchPathSuffix(value, argv[1]);
}
return value === argv[index + 1];
});
}
function matchPathSuffix(pathSuffix, path) {
if (pathSuffix.startsWith(INTERPRETER_DIR_MACRO)) {
const expectedPath = require('path').dirname(process.execPath) + pathSuffix.substring(INTERPRETER_DIR_MACRO.length);
return toSystemIndependentPath(path) === toSystemIndependentPath(expectedPath);
}
return toSystemIndependentPath(path).endsWith(pathSuffix);
}
function toSystemIndependentPath(path) {
return path.replace(/\\/g, '/');
}
}
/**
* inspector.open(...,...,false) doesn't work properly on some node versions. It opens the port but debugger can't attach.
*/
function isAsyncInspectorOpenSupported() {
try {
const versions = process.versions.node.split('.');
const major = parseInt(versions[0], 10);
const minor = parseInt(versions[1], 10);
return major >= 11 || major === 10 && minor >= 7;
}
catch (e) {
console.error(formatMessage('Cannot parse node version: ' + JSON.stringify(process.versions)), e);
return false;
}
}
function findAvailablePort(inspector, asyncInspectorOpenSupported) {
try {
inspector.open(0, getBindHost(), false);
const port = parsePort(inspector.url());
if (!asyncInspectorOpenSupported) {
inspector.close();
}
return port;
}
catch(e) {
inspector.close();
throw e;
}
/**
* @param {String} url, like 'ws://127.0.0.1:45681/98f5f884-7eba-4037-aa71-ba972e8ad0dd'
*/
function parsePort(url) {
const { URL } = require('url');
const parsedURL = new URL(url);
const port = parseInt(parsedURL.port, 10);
if (isNaN(port)) throw Error('Failed to parse debug port from ' + url);
return port;
}
}
/**
* @return host to listen on for inspector connections. If undefined, localhost will be used.
*/
function getBindHost() {
return process.env.JETBRAINS_NODE_BIND_HOST;
}
function log(message) {
console.log(formatMessage(message));
}
function formatMessage(message) {
return debugConnectorUtil.formatMessage('debugConnector', message);
}