Skip to content

Commit 18ce2b8

Browse files
addaleaxtargos
authored andcommitted
cli: generate --help text in JS
Instead of having a custom, static, hand-written string that is being printed to stdout when `--help` is present, generate it in JS when requested. Backport-PR-URL: #22644 PR-URL: #22490 Reviewed-By: Michaël Zasso <[email protected]>
1 parent b829958 commit 18ce2b8

File tree

8 files changed

+174
-159
lines changed

8 files changed

+174
-159
lines changed

lib/internal/bootstrap/node.js

+5
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@
114114
NativeModule.require('internal/inspector_async_hook').setup();
115115
}
116116

117+
if (internalBinding('options').getOptions('--help')) {
118+
NativeModule.require('internal/print_help').print(process.stdout);
119+
return;
120+
}
121+
117122
if (isMainThread) {
118123
mainThreadSetup.setupChildProcessIpcChannel();
119124
}

lib/internal/print_help.js

+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
'use strict';
2+
const { internalBinding } = require('internal/bootstrap/loaders');
3+
const { getOptions, types } = internalBinding('options');
4+
5+
const typeLookup = [];
6+
for (const key of Object.keys(types))
7+
typeLookup[types[key]] = key;
8+
9+
// Environment variables are parsed ad-hoc throughout the code base,
10+
// so we gather the documentation here.
11+
const { hasIntl, hasSmallICU, hasNodeOptions } = process.binding('config');
12+
const envVars = new Map([
13+
['NODE_DEBUG', { helpText: "','-separated list of core modules that " +
14+
'should print debug information' }],
15+
['NODE_DEBUG_NATIVE', { helpText: "','-separated list of C++ core debug " +
16+
'categories that should print debug output' }],
17+
['NODE_DISABLE_COLORS', { helpText: 'set to 1 to disable colors in ' +
18+
'the REPL' }],
19+
['NODE_EXTRA_CA_CERTS', { helpText: 'path to additional CA certificates ' +
20+
'file' }],
21+
['NODE_NO_WARNINGS', { helpText: 'set to 1 to silence process warnings' }],
22+
['NODE_PATH', { helpText: `'${require('path').delimiter}'-separated list ` +
23+
'of directories prefixed to the module search path' }],
24+
['NODE_PENDING_DEPRECATION', { helpText: 'set to 1 to emit pending ' +
25+
'deprecation warnings' }],
26+
['NODE_PRESERVE_SYMLINKS', { helpText: 'set to 1 to preserve symbolic ' +
27+
'links when resolving and caching modules' }],
28+
['NODE_REDIRECT_WARNINGS', { helpText: 'write warnings to path instead ' +
29+
'of stderr' }],
30+
['NODE_REPL_HISTORY', { helpText: 'path to the persistent REPL ' +
31+
'history file' }],
32+
['OPENSSL_CONF', { helpText: 'load OpenSSL configuration from file' }]
33+
].concat(hasIntl ? [
34+
['NODE_ICU_DATA', { helpText: 'data path for ICU (Intl object) data' +
35+
hasSmallICU ? '' : ' (will extend linked-in data)' }]
36+
] : []).concat(hasNodeOptions ? [
37+
['NODE_OPTIONS', { helpText: 'set CLI options in the environment via a ' +
38+
'space-separated list' }]
39+
] : []));
40+
41+
42+
function indent(text, depth) {
43+
return text.replace(/^/gm, ' '.repeat(depth));
44+
}
45+
46+
function fold(text, width) {
47+
return text.replace(new RegExp(`([^\n]{0,${width}})( |$)`, 'g'),
48+
(_, newLine, end) => newLine + (end === ' ' ? '\n' : ''));
49+
}
50+
51+
function getArgDescription(type) {
52+
switch (typeLookup[type]) {
53+
case 'kNoOp':
54+
case 'kV8Option':
55+
case 'kBoolean':
56+
break;
57+
case 'kHostPort':
58+
return '[host:]port';
59+
case 'kInteger':
60+
case 'kString':
61+
case 'kStringList':
62+
return '...';
63+
case undefined:
64+
break;
65+
default:
66+
require('assert').fail(`unknown option type ${type}`);
67+
}
68+
}
69+
70+
function format({ options, aliases = new Map(), firstColumn, secondColumn }) {
71+
let text = '';
72+
73+
for (const [
74+
name, { helpText, type, value }
75+
] of [...options.entries()].sort()) {
76+
if (!helpText) continue;
77+
78+
let displayName = name;
79+
const argDescription = getArgDescription(type);
80+
if (argDescription)
81+
displayName += `=${argDescription}`;
82+
83+
for (const [ from, to ] of aliases) {
84+
// For cases like e.g. `-e, --eval`.
85+
if (to[0] === name && to.length === 1) {
86+
displayName = `${from}, ${displayName}`;
87+
}
88+
89+
// For cases like `--inspect-brk[=[host:]port]`.
90+
const targetInfo = options.get(to[0]);
91+
const targetArgDescription =
92+
targetInfo ? getArgDescription(targetInfo.type) : '...';
93+
if (from === `${name}=`) {
94+
displayName += `[=${targetArgDescription}]`;
95+
} else if (from === `${name} <arg>`) {
96+
displayName += ` [${targetArgDescription}]`;
97+
}
98+
}
99+
100+
let displayHelpText = helpText;
101+
if (value === true) {
102+
// Mark boolean options we currently have enabled.
103+
// In particular, it indicates whether --use-openssl-ca
104+
// or --use-bundled-ca is the (current) default.
105+
displayHelpText += ' (currently set)';
106+
}
107+
108+
text += displayName;
109+
if (displayName.length >= firstColumn)
110+
text += '\n' + ' '.repeat(firstColumn);
111+
else
112+
text += ' '.repeat(firstColumn - displayName.length);
113+
114+
text += indent(fold(displayHelpText, secondColumn),
115+
firstColumn).trimLeft() + '\n';
116+
}
117+
118+
return text;
119+
}
120+
121+
function print(stream) {
122+
const { options, aliases } = getOptions();
123+
124+
// TODO(addaleax): Allow a bit of expansion depending on `stream.columns`
125+
// if it is set.
126+
const firstColumn = 28;
127+
const secondColumn = 40;
128+
129+
options.set('-', { helpText: 'script read from stdin (default; ' +
130+
'interactive mode if a tty)' });
131+
options.set('--', { helpText: 'indicate the end of node options' });
132+
stream.write(
133+
'Usage: node [options] [ -e script | script.js | - ] [arguments]\n' +
134+
' node inspect script.js [arguments]\n\n' +
135+
'Options:\n');
136+
stream.write(indent(format({
137+
options, aliases, firstColumn, secondColumn
138+
}), 2));
139+
140+
stream.write('\nEnvironment variables:\n');
141+
142+
stream.write(format({
143+
options: envVars, firstColumn, secondColumn
144+
}));
145+
146+
stream.write('\nDocumentation can be found at https://nodejs.org/\n');
147+
}
148+
149+
module.exports = {
150+
print
151+
};

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
'lib/internal/modules/esm/translators.js',
133133
'lib/internal/safe_globals.js',
134134
'lib/internal/net.js',
135+
'lib/internal/print_help.js',
135136
'lib/internal/process/esm_loader.js',
136137
'lib/internal/process/main_thread_only.js',
137138
'lib/internal/process/next_tick.js',

src/node.cc

-154
Original file line numberDiff line numberDiff line change
@@ -2348,155 +2348,6 @@ void LoadEnvironment(Environment* env) {
23482348
}
23492349
}
23502350

2351-
static void PrintHelp() {
2352-
printf("Usage: node [options] [ -e script | script.js | - ] [arguments]\n"
2353-
" node inspect script.js [arguments]\n"
2354-
"\n"
2355-
"Options:\n"
2356-
" - script read from stdin (default; \n"
2357-
" interactive mode if a tty)\n"
2358-
" -- indicate the end of node options\n"
2359-
" --abort-on-uncaught-exception\n"
2360-
" aborting instead of exiting causes a\n"
2361-
" core file to be generated for analysis\n"
2362-
#if HAVE_OPENSSL && NODE_FIPS_MODE
2363-
" --enable-fips enable FIPS crypto at startup\n"
2364-
#endif // NODE_FIPS_MODE && NODE_FIPS_MODE
2365-
" --experimental-modules experimental ES Module support\n"
2366-
" and caching modules\n"
2367-
" --experimental-repl-await experimental await keyword support\n"
2368-
" in REPL\n"
2369-
" --experimental-vm-modules experimental ES Module support\n"
2370-
" in vm module\n"
2371-
" --experimental-worker experimental threaded Worker support\n"
2372-
#if HAVE_OPENSSL && NODE_FIPS_MODE
2373-
" --force-fips force FIPS crypto (cannot be disabled)\n"
2374-
#endif // HAVE_OPENSSL && NODE_FIPS_MODE
2375-
#if defined(NODE_HAVE_I18N_SUPPORT)
2376-
" --icu-data-dir=dir set ICU data load path to dir\n"
2377-
" (overrides NODE_ICU_DATA)\n"
2378-
#if !defined(NODE_HAVE_SMALL_ICU)
2379-
" note: linked-in ICU data is present\n"
2380-
#endif
2381-
#endif // defined(NODE_HAVE_I18N_SUPPORT)
2382-
#if HAVE_INSPECTOR
2383-
" --inspect-brk[=[host:]port]\n"
2384-
" activate inspector on host:port\n"
2385-
" and break at start of user script\n"
2386-
" --inspect-port=[host:]port\n"
2387-
" set host:port for inspector\n"
2388-
" --inspect[=[host:]port] activate inspector on host:port\n"
2389-
" (default: 127.0.0.1:9229)\n"
2390-
#endif // HAVE_INSPECTOR
2391-
" --loader=file (with --experimental-modules) use the \n"
2392-
" specified file as a custom loader\n"
2393-
" for ECMAScript Modules \n"
2394-
" --napi-modules load N-API modules (no-op - option\n"
2395-
" kept for compatibility)\n"
2396-
" --no-deprecation silence deprecation warnings\n"
2397-
" --no-force-async-hooks-checks\n"
2398-
" disable checks for async_hooks\n"
2399-
" --no-warnings silence all process warnings\n"
2400-
#if HAVE_OPENSSL
2401-
" --openssl-config=file load OpenSSL configuration from the\n"
2402-
" specified file (overrides\n"
2403-
" OPENSSL_CONF)\n"
2404-
#endif // HAVE_OPENSSL
2405-
" --pending-deprecation emit pending deprecation warnings\n"
2406-
" --preserve-symlinks preserve symbolic links when resolving\n"
2407-
" --preserve-symlinks-main preserve symbolic links when resolving\n"
2408-
" the main module\n"
2409-
" --prof generate V8 profiler output\n"
2410-
" --prof-process process V8 profiler output generated\n"
2411-
" using --prof\n"
2412-
" --redirect-warnings=file\n"
2413-
" write warnings to file instead of\n"
2414-
" stderr\n"
2415-
" --throw-deprecation throw an exception on deprecations\n"
2416-
" --title=title the process title to use on start up\n"
2417-
#if HAVE_OPENSSL
2418-
" --tls-cipher-list=val use an alternative default TLS cipher "
2419-
"list\n"
2420-
#endif // HAVE_OPENSSL
2421-
" --trace-deprecation show stack traces on deprecations\n"
2422-
" --trace-event-categories comma separated list of trace event\n"
2423-
" categories to record\n"
2424-
" --trace-event-file-pattern Template string specifying the\n"
2425-
" filepath for the trace-events data, it\n"
2426-
" supports ${rotation} and ${pid}\n"
2427-
" log-rotation id. %%2$u is the pid.\n"
2428-
" --trace-events-enabled track trace events\n"
2429-
" --trace-sync-io show stack trace when use of sync IO\n"
2430-
" is detected after the first tick\n"
2431-
" --trace-warnings show stack traces on process warnings\n"
2432-
" --track-heap-objects track heap object allocations for heap "
2433-
"snapshots\n"
2434-
#if HAVE_OPENSSL
2435-
" --use-bundled-ca use bundled CA store"
2436-
#if !defined(NODE_OPENSSL_CERT_STORE)
2437-
" (default)"
2438-
#endif
2439-
"\n"
2440-
" --use-openssl-ca use OpenSSL's default CA store"
2441-
#if defined(NODE_OPENSSL_CERT_STORE)
2442-
" (default)"
2443-
#endif
2444-
#endif // HAVE_OPENSSL
2445-
"\n"
2446-
" --v8-options print v8 command line options\n"
2447-
" --v8-pool-size=num set v8's thread pool size\n"
2448-
" --zero-fill-buffers automatically zero-fill all newly "
2449-
"allocated\n"
2450-
" Buffer and SlowBuffer instances\n"
2451-
" -c, --check syntax check script without executing\n"
2452-
" -e, --eval script evaluate script\n"
2453-
" -h, --help print node command line options\n"
2454-
" -i, --interactive always enter the REPL even if stdin\n"
2455-
" does not appear to be a terminal\n"
2456-
" -p, --print evaluate script and print result\n"
2457-
" -r, --require module to preload (option can be "
2458-
"repeated)\n"
2459-
" -v, --version print Node.js version\n"
2460-
"\n"
2461-
"Environment variables:\n"
2462-
"NODE_DEBUG ','-separated list of core modules\n"
2463-
" that should print debug information\n"
2464-
"NODE_DEBUG_NATIVE ','-separated list of C++ core debug\n"
2465-
" categories that should print debug\n"
2466-
" output\n"
2467-
"NODE_DISABLE_COLORS set to 1 to disable colors in the REPL\n"
2468-
"NODE_EXTRA_CA_CERTS path to additional CA certificates\n"
2469-
" file\n"
2470-
#if defined(NODE_HAVE_I18N_SUPPORT)
2471-
"NODE_ICU_DATA data path for ICU (Intl object) data\n"
2472-
#if !defined(NODE_HAVE_SMALL_ICU)
2473-
" (will extend linked-in data)\n"
2474-
#endif
2475-
#endif // defined(NODE_HAVE_I18N_SUPPORT)
2476-
"NODE_NO_WARNINGS set to 1 to silence process warnings\n"
2477-
#if !defined(NODE_WITHOUT_NODE_OPTIONS)
2478-
"NODE_OPTIONS set CLI options in the environment\n"
2479-
" via a space-separated list\n"
2480-
#endif // !defined(NODE_WITHOUT_NODE_OPTIONS)
2481-
#ifdef _WIN32
2482-
"NODE_PATH ';'-separated list of directories\n"
2483-
#else
2484-
"NODE_PATH ':'-separated list of directories\n"
2485-
#endif
2486-
" prefixed to the module search path\n"
2487-
"NODE_PENDING_DEPRECATION set to 1 to emit pending deprecation\n"
2488-
" warnings\n"
2489-
"NODE_PRESERVE_SYMLINKS set to 1 to preserve symbolic links\n"
2490-
" when resolving and caching modules\n"
2491-
"NODE_REDIRECT_WARNINGS write warnings to path instead of\n"
2492-
" stderr\n"
2493-
"NODE_REPL_HISTORY path to the persistent REPL history\n"
2494-
" file\n"
2495-
"OPENSSL_CONF load OpenSSL configuration from file\n"
2496-
"\n"
2497-
"Documentation can be found at https://nodejs.org/\n");
2498-
}
2499-
25002351

25012352
static void StartInspector(Environment* env, const char* path,
25022353
std::shared_ptr<DebugOptions> debug_options) {
@@ -2772,11 +2623,6 @@ void ProcessArgv(std::vector<std::string>* args,
27722623
exit(0);
27732624
}
27742625

2775-
if (per_process_opts->print_help) {
2776-
PrintHelp();
2777-
exit(0);
2778-
}
2779-
27802626
if (per_process_opts->print_v8_help) {
27812627
V8::SetFlagsFromString("--help", 6);
27822628
exit(0);

src/node_config.cc

+4
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ static void Initialize(Local<Object> target,
7373
READONLY_BOOLEAN_PROPERTY("hasTracing");
7474
#endif
7575

76+
#if !defined(NODE_WITHOUT_NODE_OPTIONS)
77+
READONLY_BOOLEAN_PROPERTY("hasNodeOptions");
78+
#endif
79+
7680
// TODO(addaleax): This seems to be an unused, private API. Remove it?
7781
READONLY_STRING_PROPERTY(target, "icuDataDir",
7882
per_process_opts->icu_data_dir);

src/node_options.cc

+10-2
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,19 @@ PerProcessOptionsParser::PerProcessOptionsParser() {
253253
&PerProcessOptions::tls_cipher_list,
254254
kAllowedInEnvironment);
255255
AddOption("--use-openssl-ca",
256-
"use OpenSSL's default CA store",
256+
"use OpenSSL's default CA store"
257+
#if defined(NODE_OPENSSL_CERT_STORE)
258+
" (default)"
259+
#endif
260+
,
257261
&PerProcessOptions::use_openssl_ca,
258262
kAllowedInEnvironment);
259263
AddOption("--use-bundled-ca",
260-
"use bundled CA store",
264+
"use bundled CA store"
265+
#if !defined(NODE_OPENSSL_CERT_STORE)
266+
" (default)"
267+
#endif
268+
,
261269
&PerProcessOptions::use_bundled_ca,
262270
kAllowedInEnvironment);
263271
// Similar to [has_eval_string] above, except that the separation between

test/parallel/test-bootstrap-modules.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ const list = process.moduleLoadList.slice();
1111

1212
const assert = require('assert');
1313

14-
assert(list.length <= 73, list);
14+
assert(list.length <= 74, list);

test/parallel/test-cli-node-print-help.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ function validateNodePrintHelp() {
2727

2828
const cliHelpOptions = [
2929
{ compileConstant: HAVE_OPENSSL,
30-
flags: [ '--openssl-config=file', '--tls-cipher-list=val',
30+
flags: [ '--openssl-config=...', '--tls-cipher-list=...',
3131
'--use-bundled-ca', '--use-openssl-ca' ] },
3232
{ compileConstant: NODE_FIPS_MODE,
3333
flags: [ '--enable-fips', '--force-fips' ] },
3434
{ compileConstant: NODE_HAVE_I18N_SUPPORT,
35-
flags: [ '--icu-data-dir=dir', 'NODE_ICU_DATA' ] },
35+
flags: [ '--icu-data-dir=...', 'NODE_ICU_DATA' ] },
3636
{ compileConstant: HAVE_INSPECTOR,
3737
flags: [ '--inspect-brk[=[host:]port]', '--inspect-port=[host:]port',
3838
'--inspect[=[host:]port]' ] },

0 commit comments

Comments
 (0)