Skip to content

Commit b851d9f

Browse files
committed
src: add internal GetOptionsAsFlags
1 parent b87312b commit b851d9f

File tree

7 files changed

+249
-0
lines changed

7 files changed

+249
-0
lines changed

lib/internal/options.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const {
1111
const {
1212
getCLIOptionsValues,
1313
getCLIOptionsInfo,
14+
getOptionsAsFlags,
1415
getEmbedderOptions: getEmbedderOptionsFromBinding,
1516
getEnvOptionsInputType,
1617
getNamespaceOptionsInputType,
@@ -21,6 +22,7 @@ let warnOnAllowUnauthorized = true;
2122
let optionsDict;
2223
let cliInfo;
2324
let embedderOptions;
25+
let optionsFlags;
2426

2527
// getCLIOptionsValues() would serialize the option values from C++ land.
2628
// It would error if the values are queried before bootstrap is
@@ -34,6 +36,10 @@ function getCLIOptionsInfoFromBinding() {
3436
return cliInfo ??= getCLIOptionsInfo();
3537
}
3638

39+
function getOptionsAsFlagsFromBinding() {
40+
return optionsFlags ??= getOptionsAsFlags();
41+
}
42+
3743
function getEmbedderOptions() {
3844
return embedderOptions ??= getEmbedderOptionsFromBinding();
3945
}
@@ -156,6 +162,7 @@ function getAllowUnauthorized() {
156162
module.exports = {
157163
getCLIOptionsInfo: getCLIOptionsInfoFromBinding,
158164
getOptionValue,
165+
getOptionsAsFlagsFromBinding,
159166
getAllowUnauthorized,
160167
getEmbedderOptions,
161168
generateConfigJsonSchema,

src/node_options.cc

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <string_view>
2222
#include <vector>
2323

24+
using v8::Array;
2425
using v8::Boolean;
2526
using v8::Context;
2627
using v8::FunctionCallbackInfo;
@@ -1867,6 +1868,117 @@ void GetNamespaceOptionsInputType(const FunctionCallbackInfo<Value>& args) {
18671868
args.GetReturnValue().Set(namespaces_map);
18681869
}
18691870

1871+
// Return an array containing all currently active options as flag
1872+
// strings from all sources (command line, NODE_OPTIONS, config file)
1873+
void GetOptionsAsFlags(const FunctionCallbackInfo<Value>& args) {
1874+
Isolate* isolate = args.GetIsolate();
1875+
Local<Context> context = isolate->GetCurrentContext();
1876+
Environment* env = Environment::GetCurrent(context);
1877+
1878+
if (!env->has_run_bootstrapping_code()) {
1879+
// No code because this is an assertion.
1880+
THROW_ERR_OPTIONS_BEFORE_BOOTSTRAPPING(
1881+
isolate, "Should not query options before bootstrapping is done");
1882+
}
1883+
env->set_has_serialized_options(true);
1884+
1885+
Mutex::ScopedLock lock(per_process::cli_options_mutex);
1886+
IterateCLIOptionsScope s(env);
1887+
1888+
std::vector<std::string> flags;
1889+
PerProcessOptions* opts = per_process::cli_options.get();
1890+
1891+
for (const auto& item : _ppop_instance.options_) {
1892+
const std::string& option_name = item.first;
1893+
const auto& option_info = item.second;
1894+
auto field = option_info.field;
1895+
1896+
// TODO(pmarchini): Skip internal options for the moment as probably not
1897+
// required
1898+
if (option_name.empty() || option_name.starts_with('[')) {
1899+
continue;
1900+
}
1901+
1902+
// Skip V8 options and NoOp options - only Node.js-specific options
1903+
if (option_info.type == kNoOp || option_info.type == kV8Option) {
1904+
continue;
1905+
}
1906+
1907+
switch (option_info.type) {
1908+
case kBoolean: {
1909+
bool current_value = *_ppop_instance.Lookup<bool>(field, opts);
1910+
// For boolean options with default_is_true, we want the opposite logic
1911+
if (option_info.default_is_true) {
1912+
if (!current_value) {
1913+
// If default is true and current is false, add --no-* flag
1914+
flags.push_back("--no-" + option_name.substr(2));
1915+
}
1916+
} else {
1917+
if (current_value) {
1918+
// If default is false and current is true, add --flag
1919+
flags.push_back(option_name);
1920+
}
1921+
}
1922+
break;
1923+
}
1924+
case kInteger: {
1925+
int64_t current_value = *_ppop_instance.Lookup<int64_t>(field, opts);
1926+
flags.push_back(option_name + "=" + std::to_string(current_value));
1927+
break;
1928+
}
1929+
case kUInteger: {
1930+
uint64_t current_value = *_ppop_instance.Lookup<uint64_t>(field, opts);
1931+
flags.push_back(option_name + "=" + std::to_string(current_value));
1932+
break;
1933+
}
1934+
case kString: {
1935+
const std::string& current_value =
1936+
*_ppop_instance.Lookup<std::string>(field, opts);
1937+
// Only include if not empty
1938+
if (!current_value.empty()) {
1939+
flags.push_back(option_name + "=" + current_value);
1940+
}
1941+
break;
1942+
}
1943+
case kStringList: {
1944+
const std::vector<std::string>& current_values =
1945+
*_ppop_instance.Lookup<StringVector>(field, opts);
1946+
// Add each string in the list as a separate flag
1947+
for (const std::string& value : current_values) {
1948+
flags.push_back(option_name + "=" + value);
1949+
}
1950+
break;
1951+
}
1952+
case kHostPort: {
1953+
const HostPort& host_port =
1954+
*_ppop_instance.Lookup<HostPort>(field, opts);
1955+
// Only include if host is not empty or port is not default
1956+
if (!host_port.host().empty() || host_port.port() != 0) {
1957+
std::string host_port_str = host_port.host();
1958+
if (host_port.port() != 0) {
1959+
if (!host_port_str.empty()) {
1960+
host_port_str += ":";
1961+
}
1962+
host_port_str += std::to_string(host_port.port());
1963+
}
1964+
if (!host_port_str.empty()) {
1965+
flags.push_back(option_name + "=" + host_port_str);
1966+
}
1967+
}
1968+
break;
1969+
}
1970+
default:
1971+
// Skip unknown types
1972+
break;
1973+
}
1974+
}
1975+
1976+
Local<Value> result;
1977+
CHECK(ToV8Value(context, flags).ToLocal(&result));
1978+
1979+
args.GetReturnValue().Set(result);
1980+
}
1981+
18701982
void Initialize(Local<Object> target,
18711983
Local<Value> unused,
18721984
Local<Context> context,
@@ -1877,6 +1989,8 @@ void Initialize(Local<Object> target,
18771989
context, target, "getCLIOptionsValues", GetCLIOptionsValues);
18781990
SetMethodNoSideEffect(
18791991
context, target, "getCLIOptionsInfo", GetCLIOptionsInfo);
1992+
SetMethodNoSideEffect(
1993+
context, target, "getOptionsAsFlags", GetOptionsAsFlags);
18801994
SetMethodNoSideEffect(
18811995
context, target, "getEmbedderOptions", GetEmbedderOptions);
18821996
SetMethodNoSideEffect(
@@ -1909,6 +2023,7 @@ void Initialize(Local<Object> target,
19092023
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
19102024
registry->Register(GetCLIOptionsValues);
19112025
registry->Register(GetCLIOptionsInfo);
2026+
registry->Register(GetOptionsAsFlags);
19122027
registry->Register(GetEmbedderOptions);
19132028
registry->Register(GetEnvOptionsInputType);
19142029
registry->Register(GetNamespaceOptionsInputType);

src/node_options.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,8 @@ class OptionsParser {
649649
friend std::vector<std::string> MapAvailableNamespaces();
650650
friend void GetEnvOptionsInputType(
651651
const v8::FunctionCallbackInfo<v8::Value>& args);
652+
friend void GetOptionsAsFlags(
653+
const v8::FunctionCallbackInfo<v8::Value>& args);
652654
};
653655

654656
using StringVector = std::vector<std::string>;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
NODE_OPTIONS=--secure-heap=8
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const { getOptionsAsFlagsFromBinding } = require('internal/options');
2+
3+
const flags = getOptionsAsFlagsFromBinding();
4+
console.log(JSON.stringify(flags.sort()));
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"nodeOptions": {
3+
"experimental-transform-types": true,
4+
"max-http-header-size": 8192
5+
},
6+
"testRunner": {
7+
"test-isolation": "none"
8+
}
9+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
'use strict';
2+
3+
const {
4+
spawnPromisified,
5+
} = require('../common');
6+
const fixtures = require('../common/fixtures');
7+
const { strictEqual } = require('node:assert');
8+
const { describe, it } = require('node:test');
9+
const path = require('node:path');
10+
11+
const fixtureFile = fixtures.path(path.join('options-as-flags', 'fixture.cjs'));
12+
const configFile = fixtures.path(path.join('options-as-flags', 'test-config.json'));
13+
const envFile = fixtures.path(path.join('options-as-flags', '.test.env'));
14+
15+
describe('getOptionsAsFlagsFromBinding', () => {
16+
it('should extract flags from command line arguments', async () => {
17+
const result = await spawnPromisified(process.execPath, [
18+
'--no-warnings',
19+
'--expose-internals',
20+
'--stack-trace-limit=512',
21+
fixtureFile,
22+
]);
23+
24+
strictEqual(result.code, 0);
25+
const flags = JSON.parse(result.stdout.trim());
26+
27+
strictEqual(flags.includes('--no-warnings'), true);
28+
strictEqual(flags.includes('--stack-trace-limit=512'), true);
29+
});
30+
31+
it('should extract flags from NODE_OPTIONS environment variable', async () => {
32+
const result = await spawnPromisified(process.execPath, [
33+
'--no-warnings',
34+
'--expose-internals',
35+
fixtureFile,
36+
], {
37+
env: {
38+
...process.env,
39+
NODE_OPTIONS: '--stack-trace-limit=4096'
40+
}
41+
});
42+
43+
strictEqual(result.code, 0);
44+
const flags = JSON.parse(result.stdout.trim());
45+
46+
// Should contain the flag from NODE_OPTIONS
47+
strictEqual(flags.includes('--stack-trace-limit=4096'), true);
48+
// Should also contain command line flags
49+
strictEqual(flags.includes('--no-warnings'), true);
50+
});
51+
52+
it('should extract flags from config file', async () => {
53+
const result = await spawnPromisified(process.execPath, [
54+
'--no-warnings',
55+
'--expose-internals',
56+
'--experimental-config-file',
57+
configFile,
58+
fixtureFile,
59+
]);
60+
61+
strictEqual(result.code, 0);
62+
const flags = JSON.parse(result.stdout.trim());
63+
64+
// Should contain flags from config file
65+
strictEqual(flags.includes('--experimental-transform-types'), true);
66+
strictEqual(flags.includes('--max-http-header-size=8192'), true);
67+
strictEqual(flags.includes('--test-isolation=none'), true);
68+
// Should also contain command line flags
69+
strictEqual(flags.includes('--no-warnings'), true);
70+
});
71+
72+
it('should extract flags from config file and command line', async () => {
73+
const result = await spawnPromisified(process.execPath, [
74+
'--no-warnings',
75+
'--expose-internals',
76+
'--stack-trace-limit=512',
77+
'--experimental-config-file',
78+
configFile,
79+
fixtureFile,
80+
]);
81+
82+
strictEqual(result.code, 0);
83+
const flags = JSON.parse(result.stdout.trim());
84+
85+
// Should contain flags from command line arguments
86+
strictEqual(flags.includes('--no-warnings'), true);
87+
strictEqual(flags.includes('--stack-trace-limit=512'), true);
88+
89+
// Should contain flags from config file
90+
strictEqual(flags.includes('--experimental-transform-types'), true);
91+
strictEqual(flags.includes('--max-http-header-size=8192'), true);
92+
strictEqual(flags.includes('--test-isolation=none'), true);
93+
});
94+
95+
it('should extract flags from .env file', async () => {
96+
const result = await spawnPromisified(process.execPath, [
97+
'--no-warnings',
98+
'--expose-internals',
99+
`--env-file=${envFile}`,
100+
fixtureFile,
101+
]);
102+
103+
strictEqual(result.code, 0);
104+
const flags = JSON.parse(result.stdout.trim());
105+
106+
// Should contain flags from .env file (NODE_OPTIONS)
107+
strictEqual(flags.includes('--secure-heap=8'), true);
108+
// Should also contain command line flags
109+
strictEqual(flags.includes('--no-warnings'), true);
110+
});
111+
});

0 commit comments

Comments
 (0)