From d462f87746cc02147e6f55377ad6b6ec8d307c92 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 20 Aug 2025 19:03:49 +0200 Subject: [PATCH 1/3] 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. --- src/node_options.cc | 4 - ...utable-application-inspect-in-sea-flags.js | 69 ++++++++++ ...t-single-executable-application-inspect.js | 122 ++++++++++++++++++ 3 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 test/sequential/test-single-executable-application-inspect-in-sea-flags.js create mode 100644 test/sequential/test-single-executable-application-inspect.js diff --git a/src/node_options.cc b/src/node_options.cc index 5cf7a9cbf58c7e..d74ed1ca5afe53 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -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, diff --git a/test/sequential/test-single-executable-application-inspect-in-sea-flags.js b/test/sequential/test-single-executable-application-inspect-in-sea-flags.js new file mode 100644 index 00000000000000..66a07e03bc1a47 --- /dev/null +++ b/test/sequential/test-single-executable-application-inspect-in-sea-flags.js @@ -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, + } +); diff --git a/test/sequential/test-single-executable-application-inspect.js b/test/sequential/test-single-executable-application-inspect.js new file mode 100644 index 00000000000000..1503759b489f4a --- /dev/null +++ b/test/sequential/test-single-executable-application-inspect.js @@ -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; +}); From 5b98c073fd0c4bc230b002dfe7d5ef2e650cbbf5 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 25 Aug 2025 15:46:53 +0200 Subject: [PATCH 2/3] fixup! sea: allow using inspector command line flags with SEA --- .../test-single-executable-application-inspect-in-sea-flags.js | 2 +- test/sequential/test-single-executable-application-inspect.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/sequential/test-single-executable-application-inspect-in-sea-flags.js b/test/sequential/test-single-executable-application-inspect-in-sea-flags.js index 66a07e03bc1a47..32a0ad34848e80 100644 --- a/test/sequential/test-single-executable-application-inspect-in-sea-flags.js +++ b/test/sequential/test-single-executable-application-inspect-in-sea-flags.js @@ -44,7 +44,7 @@ spawnSyncAndExitWithoutError( assert(existsSync(seaPrepBlob)); // Generate the SEA executable -generateSEA(outputFile, process.execPath, seaPrepBlob, true); +generateSEA(outputFile, process.execPath, seaPrepBlob); // Spawn the SEA with inspect option spawnSyncAndAssert( diff --git a/test/sequential/test-single-executable-application-inspect.js b/test/sequential/test-single-executable-application-inspect.js index 1503759b489f4a..009621ddf78b4d 100644 --- a/test/sequential/test-single-executable-application-inspect.js +++ b/test/sequential/test-single-executable-application-inspect.js @@ -44,7 +44,7 @@ spawnSyncAndExitWithoutError( assert(existsSync(seaPrepBlob)); // Generate the SEA executable -generateSEA(outputFile, process.execPath, seaPrepBlob, true); +generateSEA(outputFile, process.execPath, seaPrepBlob); // Spawn the SEA with inspect option const seaProcess = spawn(outputFile, [], { From efb83764dcd8985cadbee6f612689ae547f80b05 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 25 Aug 2025 16:20:16 +0200 Subject: [PATCH 3/3] fixup! fixup! sea: allow using inspector command line flags with SEA --- test/sequential/sequential.status | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/sequential/sequential.status b/test/sequential/sequential.status index 439fa2e3cd0f22..63414f94193595 100644 --- a/test/sequential/sequential.status +++ b/test/sequential/sequential.status @@ -61,6 +61,8 @@ test-single-executable-application-disable-experimental-sea-warning: SKIP test-single-executable-application-empty: SKIP test-single-executable-application-exec-argv: SKIP test-single-executable-application-exec-argv-empty: SKIP +test-single-executable-application-inspect-in-sea-flags: SKIP +test-single-executable-application-inspect: SKIP test-single-executable-application-snapshot: SKIP test-single-executable-application-snapshot-and-code-cache: SKIP test-single-executable-application-snapshot-worker: SKIP