sea: allow using inspector command line flags with SEA · nodejs/node@bf26b47
1+'use strict';
2+3+// This tests the creation of a single executable application that can be
4+// debugged using the inspector protocol with NODE_OPTIONS=--inspect-brk=0
5+6+require('../common');
7+const assert = require('assert');
8+const { writeFileSync, existsSync } = require('fs');
9+const { spawn } = require('child_process');
10+const tmpdir = require('../common/tmpdir');
11+const { spawnSyncAndExitWithoutError } = require('../common/child_process');
12+13+const {
14+ generateSEA,
15+ skipIfSingleExecutableIsNotSupported,
16+} = require('../common/sea');
17+18+skipIfSingleExecutableIsNotSupported();
19+20+const configFile = tmpdir.resolve('sea-config.json');
21+const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
22+const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');
23+24+tmpdir.refresh();
25+26+// Create a simple hello world script
27+writeFileSync(tmpdir.resolve('hello.js'), `console.log('Hello, world!');`, 'utf-8');
28+29+// Create SEA configuration
30+writeFileSync(configFile, `
31+{
32+ "main": "hello.js",
33+ "output": "sea-prep.blob"
34+}
35+`);
36+37+// Generate the SEA prep blob
38+spawnSyncAndExitWithoutError(
39+process.execPath,
40+['--experimental-sea-config', 'sea-config.json'],
41+{ cwd: tmpdir.path }
42+);
43+44+assert(existsSync(seaPrepBlob));
45+46+// Generate the SEA executable
47+generateSEA(outputFile, process.execPath, seaPrepBlob);
48+49+// Spawn the SEA with inspect option
50+const seaProcess = spawn(outputFile, [], {
51+env: {
52+ ...process.env,
53+NODE_OPTIONS: '--inspect-brk=0',
54+},
55+});
56+57+let debuggerUrl = null;
58+let seaStderr = '';
59+60+seaProcess.stderr.setEncoding('utf8');
61+seaProcess.stdout.setEncoding('utf8');
62+63+seaProcess.stdout.on('data', (data) => {
64+console.log(`[SEA][STDOUT] ${data}`);
65+});
66+67+seaProcess.stderr.on('data', (data) => {
68+console.log(`[SEA][STDERR] ${data}`);
69+seaStderr += data;
70+71+// Parse the debugger listening message
72+const match = seaStderr.match(/Debugger listening on ws:\/\/([\d.]+):(\d+)\//);
73+if (match && !debuggerUrl) {
74+const host = match[1];
75+const port = match[2];
76+debuggerUrl = `${host}:${port}`;
77+78+console.log(`Running ${process.execPath} inspect ${debuggerUrl}`);
79+// Once we have the debugger URL, spawn the inspector CLI
80+const inspectorProcess = spawn(process.execPath, ['inspect', debuggerUrl], {
81+stdio: ['pipe', 'pipe', 'pipe'],
82+});
83+84+let inspectorStdout = '';
85+inspectorProcess.stdout.setEncoding('utf8');
86+inspectorProcess.stderr.setEncoding('utf8');
87+88+inspectorProcess.stdout.on('data', (data) => {
89+console.log(`[INSPECT][STDOUT] ${data}`);
90+inspectorStdout += data;
91+92+// Check if we successfully connected
93+const matches = [...inspectorStdout.matchAll(/debug> /g)];
94+if (inspectorStdout.includes(`connecting to ${host}:${port} ... ok`) &&
95+matches.length >= 2) {
96+// We are at the second prompt, which means we can send commands to terminate both now.
97+console.log('Sending .exit command to inspector...');
98+inspectorProcess.stdin.write('.exit\n');
99+}
100+});
101+102+inspectorProcess.stderr.on('data', (data) => {
103+console.log(`[INSPECT][STDERR] ${data}`);
104+});
105+106+inspectorProcess.on('close', (code) => {
107+assert.strictEqual(code, 0, `Inspector process exited with code ${code}.`);
108+});
109+110+inspectorProcess.on('error', (err) => {
111+throw err;
112+});
113+}
114+});
115+116+seaProcess.on('close', (code) => {
117+assert.strictEqual(code, 0, `SEA process exited with code ${code}.`);
118+});
119+120+seaProcess.on('error', (err) => {
121+throw err;
122+});