Skip to content

Commit 07ac22c

Browse files
committed
src: support namespace options in configuration schema generation
1 parent e2a9d74 commit 07ac22c

File tree

3 files changed

+183
-50
lines changed

3 files changed

+183
-50
lines changed

doc/node-config-schema.json

+12
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,18 @@
584584
}
585585
},
586586
"type": "object"
587+
},
588+
"test_runner": {
589+
"type": "object",
590+
"additionalProperties": false,
591+
"properties": {
592+
"test-coverage-lines": {
593+
"type": "number"
594+
},
595+
"test-isolation": {
596+
"type": "string"
597+
}
598+
}
587599
}
588600
},
589601
"type": "object"

lib/internal/options.js

+57-14
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const {
1313
getCLIOptionsInfo,
1414
getEmbedderOptions: getEmbedderOptionsFromBinding,
1515
getEnvOptionsInputType,
16+
getNamespaceOptionsInputType,
1617
} = internalBinding('options');
1718

1819
let warnOnAllowUnauthorized = true;
@@ -38,7 +39,22 @@ function getEmbedderOptions() {
3839
}
3940

4041
function generateConfigJsonSchema() {
41-
const map = getEnvOptionsInputType();
42+
const envOptionsMap = getEnvOptionsInputType();
43+
const namespaceOptionsMap = getNamespaceOptionsInputType();
44+
45+
function createPropertyForType(key, type) {
46+
if (type === 'array') {
47+
return {
48+
__proto__: null,
49+
oneOf: [
50+
{ __proto__: null, type: 'string' },
51+
{ __proto__: null, items: { __proto__: null, type: 'string', minItems: 1 }, type: 'array' },
52+
],
53+
};
54+
}
55+
56+
return { __proto__: null, type };
57+
}
4258

4359
const schema = {
4460
__proto__: null,
@@ -60,31 +76,58 @@ function generateConfigJsonSchema() {
6076
type: 'object',
6177
};
6278

63-
const nodeOptions = schema.properties.nodeOptions.properties;
79+
// Get the root properties object for adding namespaces
80+
const rootProperties = schema.properties;
81+
const nodeOptions = rootProperties.nodeOptions.properties;
6482

65-
for (const { 0: key, 1: type } of map) {
83+
// Add env options to nodeOptions (backward compatibility)
84+
for (const { 0: key, 1: type } of envOptionsMap) {
6685
const keyWithoutPrefix = StringPrototypeReplace(key, '--', '');
67-
if (type === 'array') {
68-
nodeOptions[keyWithoutPrefix] = {
69-
__proto__: null,
70-
oneOf: [
71-
{ __proto__: null, type: 'string' },
72-
{ __proto__: null, items: { __proto__: null, type: 'string', minItems: 1 }, type: 'array' },
73-
],
74-
};
75-
} else {
76-
nodeOptions[keyWithoutPrefix] = { __proto__: null, type };
86+
nodeOptions[keyWithoutPrefix] = createPropertyForType(key, type);
87+
}
88+
89+
// Add namespace properties at the root level
90+
for (const { 0: namespace, 1: optionsMap } of namespaceOptionsMap) {
91+
// Create namespace object at the root level
92+
rootProperties[namespace] = {
93+
__proto__: null,
94+
type: 'object',
95+
additionalProperties: false,
96+
properties: { __proto__: null },
97+
};
98+
99+
const namespaceProperties = rootProperties[namespace].properties;
100+
101+
// Add all options for this namespace
102+
for (const { 0: optionName, 1: optionType } of optionsMap) {
103+
const keyWithoutPrefix = StringPrototypeReplace(optionName, '--', '');
104+
namespaceProperties[keyWithoutPrefix] = createPropertyForType(optionName, optionType);
77105
}
106+
107+
// Sort the namespace properties alphabetically
108+
const sortedNamespaceKeys = ArrayPrototypeSort(ObjectKeys(namespaceProperties));
109+
const sortedNamespaceProperties = ObjectFromEntries(
110+
ArrayPrototypeMap(sortedNamespaceKeys, (key) => [key, namespaceProperties[key]]),
111+
);
112+
rootProperties[namespace].properties = sortedNamespaceProperties;
78113
}
79114

80-
// Sort the proerties by key alphabetically.
115+
// Sort the top-level properties by key alphabetically
81116
const sortedKeys = ArrayPrototypeSort(ObjectKeys(nodeOptions));
82117
const sortedProperties = ObjectFromEntries(
83118
ArrayPrototypeMap(sortedKeys, (key) => [key, nodeOptions[key]]),
84119
);
85120

86121
schema.properties.nodeOptions.properties = sortedProperties;
87122

123+
// Also sort the root level properties
124+
const sortedRootKeys = ArrayPrototypeSort(ObjectKeys(rootProperties));
125+
const sortedRootProperties = ObjectFromEntries(
126+
ArrayPrototypeMap(sortedRootKeys, (key) => [key, rootProperties[key]]),
127+
);
128+
129+
schema.properties = sortedRootProperties;
130+
88131
return schema;
89132
}
90133

src/node_options.cc

+114-36
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,64 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors,
242242

243243
namespace options_parser {
244244

245+
// Helper function to convert option types to their string representation
246+
// and add them to a V8 Map
247+
static bool AddOptionTypeToMap(Isolate* isolate,
248+
Local<Context> context,
249+
Local<Map> map,
250+
const std::string& option_name,
251+
const OptionType& option_type) {
252+
std::string type;
253+
switch (static_cast<int>(option_type)) {
254+
case 0: // No-op
255+
case 1: // V8 flags
256+
break; // V8 and NoOp flags are not supported
257+
258+
case 2:
259+
type = "boolean";
260+
break;
261+
case 3: // integer
262+
case 4: // unsigned integer
263+
case 6: // host port
264+
type = "number";
265+
break;
266+
case 5: // string
267+
type = "string";
268+
break;
269+
case 7: // string array
270+
type = "array";
271+
break;
272+
default:
273+
UNREACHABLE();
274+
}
275+
276+
if (type.empty()) {
277+
return true; // Skip this entry but continue processing
278+
}
279+
280+
Local<String> option_key;
281+
if (!String::NewFromUtf8(isolate,
282+
option_name.data(),
283+
v8::NewStringType::kNormal,
284+
option_name.size())
285+
.ToLocal(&option_key)) {
286+
return true; // Skip this entry but continue processing
287+
}
288+
289+
Local<String> type_value;
290+
if (!String::NewFromUtf8(
291+
isolate, type.data(), v8::NewStringType::kNormal, type.size())
292+
.ToLocal(&type_value)) {
293+
return true; // Skip this entry but continue processing
294+
}
295+
296+
if (map->Set(context, option_key, type_value).IsEmpty()) {
297+
return false; // Error occurred, stop processing
298+
}
299+
300+
return true;
301+
}
302+
245303
class DebugOptionsParser : public OptionsParser<DebugOptions> {
246304
public:
247305
DebugOptionsParser();
@@ -1636,56 +1694,71 @@ void GetEnvOptionsInputType(const FunctionCallbackInfo<Value>& args) {
16361694
for (const auto& item : _ppop_instance.options_) {
16371695
if (!item.first.empty() && !item.first.starts_with('[') &&
16381696
item.second.env_setting == kAllowedInEnvvar) {
1639-
std::string type;
1640-
switch (static_cast<int>(item.second.type)) {
1641-
case 0: // No-op
1642-
case 1: // V8 flags
1643-
break; // V8 and NoOp flags are not supported
1644-
1645-
case 2:
1646-
type = "boolean";
1647-
break;
1648-
case 3: // integer
1649-
case 4: // unsigned integer
1650-
case 6: // host port
1651-
type = "number";
1652-
break;
1653-
case 5: // string
1654-
type = "string";
1655-
break;
1656-
case 7: // string array
1657-
type = "array";
1658-
break;
1659-
default:
1660-
UNREACHABLE();
1697+
if (!AddOptionTypeToMap(
1698+
isolate, context, flags_map, item.first, item.second.type)) {
1699+
return;
16611700
}
1701+
}
1702+
}
1703+
args.GetReturnValue().Set(flags_map);
1704+
}
16621705

1663-
if (type.empty()) {
1664-
continue;
1665-
}
1706+
// This function returns a two-level nested map containing all the available
1707+
// options grouped by their namespaces along with their input types. This is
1708+
// used for config file JSON schema generation
1709+
void GetNamespaceOptionsInputType(const FunctionCallbackInfo<Value>& args) {
1710+
Isolate* isolate = args.GetIsolate();
1711+
Local<Context> context = isolate->GetCurrentContext();
1712+
Environment* env = Environment::GetCurrent(context);
16661713

1667-
Local<String> value;
1668-
if (!String::NewFromUtf8(
1669-
isolate, type.data(), v8::NewStringType::kNormal, type.size())
1670-
.ToLocal(&value)) {
1671-
continue;
1714+
if (!env->has_run_bootstrapping_code()) {
1715+
// No code because this is an assertion.
1716+
THROW_ERR_OPTIONS_BEFORE_BOOTSTRAPPING(
1717+
isolate, "Should not query options before bootstrapping is done");
1718+
}
1719+
1720+
Mutex::ScopedLock lock(per_process::cli_options_mutex);
1721+
1722+
Local<Map> namespaces_map = Map::New(isolate);
1723+
1724+
// Get the mapping of namespaces to their options and types
1725+
auto namespace_options = options_parser::MapNamespaceOptionsAssociations();
1726+
1727+
for (const auto& ns_entry : namespace_options) {
1728+
const std::string& namespace_name = ns_entry.first;
1729+
const auto& options_map = ns_entry.second;
1730+
1731+
Local<Map> options_type_map = Map::New(isolate);
1732+
1733+
for (const auto& opt_entry : options_map) {
1734+
const std::string& option_name = opt_entry.first;
1735+
const options_parser::OptionType& option_type = opt_entry.second;
1736+
1737+
if (!AddOptionTypeToMap(
1738+
isolate, context, options_type_map, option_name, option_type)) {
1739+
return;
16721740
}
1741+
}
16731742

1674-
Local<String> field;
1743+
// Only add namespaces that have options
1744+
if (options_type_map->Size() > 0) {
1745+
Local<String> namespace_key;
16751746
if (!String::NewFromUtf8(isolate,
1676-
item.first.data(),
1747+
namespace_name.data(),
16771748
v8::NewStringType::kNormal,
1678-
item.first.size())
1679-
.ToLocal(&field)) {
1749+
namespace_name.size())
1750+
.ToLocal(&namespace_key)) {
16801751
continue;
16811752
}
16821753

1683-
if (flags_map->Set(context, field, value).IsEmpty()) {
1754+
if (namespaces_map->Set(context, namespace_key, options_type_map)
1755+
.IsEmpty()) {
16841756
return;
16851757
}
16861758
}
16871759
}
1688-
args.GetReturnValue().Set(flags_map);
1760+
1761+
args.GetReturnValue().Set(namespaces_map);
16891762
}
16901763

16911764
void Initialize(Local<Object> target,
@@ -1702,6 +1775,10 @@ void Initialize(Local<Object> target,
17021775
context, target, "getEmbedderOptions", GetEmbedderOptions);
17031776
SetMethodNoSideEffect(
17041777
context, target, "getEnvOptionsInputType", GetEnvOptionsInputType);
1778+
SetMethodNoSideEffect(context,
1779+
target,
1780+
"getNamespaceOptionsInputType",
1781+
GetNamespaceOptionsInputType);
17051782
Local<Object> env_settings = Object::New(isolate);
17061783
NODE_DEFINE_CONSTANT(env_settings, kAllowedInEnvvar);
17071784
NODE_DEFINE_CONSTANT(env_settings, kDisallowedInEnvvar);
@@ -1728,6 +1805,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
17281805
registry->Register(GetCLIOptionsInfo);
17291806
registry->Register(GetEmbedderOptions);
17301807
registry->Register(GetEnvOptionsInputType);
1808+
registry->Register(GetNamespaceOptionsInputType);
17311809
}
17321810
} // namespace options_parser
17331811

0 commit comments

Comments
 (0)