Sitelet https://github.com/nodejs/node/pull/59568/commits/d462f87746cc02147e6f55377ad6b6ec8d307c92
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
sea: allow using inspector command line flags with SEA
The inspector command line flags should be allowed via NODE_OPTIONS,
though it should not be consumed when passed directly to the SEA.
This patch allows it to consume inspector flags from NODE_OPTIONS
and add tests for different behaviors.
  • Loading branch information
joyeecheung committed Aug 25, 2025
commit d462f87746cc02147e6f55377ad6b6ec8d307c92
4 changes: 0 additions & 4 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -422,10 +422,6 @@ void Parse(
// TODO(addaleax): Make that unnecessary.

DebugOptionsParser::DebugOptionsParser() {
#ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION
if (sea::IsSingleExecutable()) return;
#endif

AddOption("--inspect-port",
"set host:port for inspector",
&DebugOptions::host_port,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use strict';

// This tests that the debugger flag --inspect passed directly to a single executable
// application would not be consumed by Node.js but passed to the application
// instead.

require('../common');
const assert = require('assert');
const { writeFileSync, existsSync } = require('fs');
const { spawnSyncAndAssert } = require('../common/child_process');
const tmpdir = require('../common/tmpdir');
const { spawnSyncAndExitWithoutError } = require('../common/child_process');

const {
generateSEA,
skipIfSingleExecutableIsNotSupported,
} = require('../common/sea');

skipIfSingleExecutableIsNotSupported();

const configFile = tmpdir.resolve('sea-config.json');
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');

tmpdir.refresh();

writeFileSync(tmpdir.resolve('sea.js'), `console.log(process.argv);`, 'utf-8');

// Create SEA configuration
writeFileSync(configFile, `
{
"main": "sea.js",
"output": "sea-prep.blob"
}
`);

// Generate the SEA prep blob
spawnSyncAndExitWithoutError(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{ cwd: tmpdir.path }
);

assert(existsSync(seaPrepBlob));

// Generate the SEA executable
generateSEA(outputFile, process.execPath, seaPrepBlob, true);

// Spawn the SEA with inspect option
spawnSyncAndAssert(
outputFile,
['--inspect=0'],
{
env: {
...process.env,
},
},
{
stdout(data) {
assert.match(data, /--inspect=0/);
return true;
},
stderr(data) {
assert.doesNotMatch(data, /Debugger listening/);
return true;
},
trim: true,
}
);
122 changes: 122 additions & 0 deletions test/sequential/test-single-executable-application-inspect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
'use strict';

// This tests the creation of a single executable application that can be
// debugged using the inspector protocol with NODE_OPTIONS=--inspect-brk=0

require('../common');
const assert = require('assert');
const { writeFileSync, existsSync } = require('fs');
const { spawn } = require('child_process');
const tmpdir = require('../common/tmpdir');
const { spawnSyncAndExitWithoutError } = require('../common/child_process');

const {
generateSEA,
skipIfSingleExecutableIsNotSupported,
} = require('../common/sea');

skipIfSingleExecutableIsNotSupported();

const configFile = tmpdir.resolve('sea-config.json');
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');

tmpdir.refresh();

// Create a simple hello world script
writeFileSync(tmpdir.resolve('hello.js'), `console.log('Hello, world!');`, 'utf-8');

// Create SEA configuration
writeFileSync(configFile, `
{
"main": "hello.js",
"output": "sea-prep.blob"
}
`);

// Generate the SEA prep blob
spawnSyncAndExitWithoutError(
process.execPath,
['--experimental-sea-config', 'sea-config.json'],
{ cwd: tmpdir.path }
);

assert(existsSync(seaPrepBlob));

// Generate the SEA executable
generateSEA(outputFile, process.execPath, seaPrepBlob, true);

// Spawn the SEA with inspect option
const seaProcess = spawn(outputFile, [], {
env: {
...process.env,
NODE_OPTIONS: '--inspect-brk=0',
},
});

let debuggerUrl = null;
let seaStderr = '';

seaProcess.stderr.setEncoding('utf8');
seaProcess.stdout.setEncoding('utf8');

seaProcess.stdout.on('data', (data) => {
console.log(`[SEA][STDOUT] ${data}`);
});

seaProcess.stderr.on('data', (data) => {
console.log(`[SEA][STDERR] ${data}`);
seaStderr += data;

// Parse the debugger listening message
const match = seaStderr.match(/Debugger listening on ws:\/\/([\d.]+):(\d+)\//);
if (match && !debuggerUrl) {
const host = match[1];
const port = match[2];
debuggerUrl = `${host}:${port}`;

console.log(`Running ${process.execPath} inspect ${debuggerUrl}`);
// Once we have the debugger URL, spawn the inspector CLI
const inspectorProcess = spawn(process.execPath, ['inspect', debuggerUrl], {
stdio: ['pipe', 'pipe', 'pipe'],
});

let inspectorStdout = '';
inspectorProcess.stdout.setEncoding('utf8');
inspectorProcess.stderr.setEncoding('utf8');

inspectorProcess.stdout.on('data', (data) => {
console.log(`[INSPECT][STDOUT] ${data}`);
inspectorStdout += data;

// Check if we successfully connected
const matches = [...inspectorStdout.matchAll(/debug> /g)];
if (inspectorStdout.includes(`connecting to ${host}:${port} ... ok`) &&
matches.length >= 2) {
// We are at the second prompt, which means we can send commands to terminate both now.
console.log('Sending .exit command to inspector...');
inspectorProcess.stdin.write('.exit\n');
}
});

inspectorProcess.stderr.on('data', (data) => {
console.log(`[INSPECT][STDERR] ${data}`);
});

inspectorProcess.on('close', (code) => {
assert.strictEqual(code, 0, `Inspector process exited with code ${code}.`);
});

inspectorProcess.on('error', (err) => {
throw err;
});
}
});

seaProcess.on('close', (code) => {
assert.strictEqual(code, 0, `SEA process exited with code ${code}.`);
});

seaProcess.on('error', (err) => {
throw err;
});