GeezerVison is showing github.com — cli: style node --help output with util.styleText · nodejs/node@fe8fa45
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/main/print_help.js +24-9Lines changed: 24 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -25,11 +25,16 @@ const { | 25 | 25 | } = require('internal/process/pre_execution'); | 26 | 26 |
| 27 | 27 | const { getCLIOptionsInfo, getOptionValue } = require('internal/options'); | | 28 | +const { styleText } = require('util'); | 28 | 29 |
| 29 | 30 | const typeLookup = []; | 30 | 31 | for (const key of ObjectKeys(types)) | 31 | 32 | typeLookup[types[key]] = key; | 32 | 33 |
| | 34 | +function style(format, text) { | | 35 | + return styleText(format, text, { stream: process.stdout }); | | 36 | +} | | 37 | + | 33 | 38 | // Environment variables are parsed ad-hoc throughout the code base, | 34 | 39 | // so we gather the documentation here. | 35 | 40 | const { hasIntl, hasSmallICU, hasNodeOptions } = internalBinding('config'); | @@ -117,7 +122,7 @@ function getArgDescription(type) { | 117 | 122 | } | 118 | 123 |
| 119 | 124 | function format( | 120 | | - { options, aliases = new SafeMap(), firstColumn, secondColumn }, | | 125 | + { options, aliases = new SafeMap(), firstColumn, secondColumn, nameStyle = [] }, | 121 | 126 | ) { | 122 | 127 | let text = ''; | 123 | 128 | let maxFirstColumnUsed = 0; | @@ -176,7 +181,7 @@ function format( | 176 | 181 | displayHelpText += ' (currently set)'; | 177 | 182 | } | 178 | 183 |
| 179 | | - text += displayName; | | 184 | + text += style(nameStyle, displayName); | 180 | 185 | maxFirstColumnUsed = MathMax(maxFirstColumnUsed, displayName.length); | 181 | 186 | if (displayName.length >= firstColumn) | 182 | 187 | text += '\n' + StringPrototypeRepeat(' ', firstColumn); | @@ -194,6 +199,7 @@ function format( | 194 | 199 | aliases, | 195 | 200 | firstColumn: maxFirstColumnUsed + 2, | 196 | 201 | secondColumn, | | 202 | + nameStyle, | 197 | 203 | }); | 198 | 204 | } | 199 | 205 |
| @@ -214,20 +220,29 @@ function print(stream) { | 214 | 220 | 'interactive mode if a tty)' }); | 215 | 221 | options.set('--', { helpText: 'indicate the end of node options' }); | 216 | 222 | let helpText = ( | 217 | | - 'Usage: node [options] [ script.js ] [arguments]\n' + | 218 | | - ' node inspect [options] [ script.js | host:port ] [arguments]\n\n' + | 219 | | - 'Options:\n'); | | 223 | + style('bold', | | 224 | + 'Usage: node [options] [ script.js ] [arguments]\n' + | | 225 | + ' node inspect [options] [ script.js | host:port ] [arguments]') + | | 226 | + '\n\n' + style('bold', 'Options:') + '\n'); | 220 | 227 | helpText += (indent(format({ | 221 | | - options, aliases, firstColumn, secondColumn, | | 228 | + options, aliases, firstColumn, secondColumn, nameStyle: ['bold', 'green'], | 222 | 229 | }), 2)); | 223 | 230 |
| 224 | | - helpText += ('\nEnvironment variables:\n'); | | 231 | + helpText += ('\n' + style('bold', 'Environment variables:') + '\n'); | 225 | 232 |
| 226 | 233 | helpText += (format({ | 227 | | - options: envVars, firstColumn, secondColumn, | | 234 | + options: envVars, firstColumn, secondColumn, nameStyle: ['bold', 'magenta'], | 228 | 235 | })); | 229 | 236 |
| 230 | | - helpText += ('\nDocumentation can be found at https://nodejs.org/'); | | 237 | + helpText += ('\nDocumentation can be found at ' + | | 238 | + style(['blue', 'underline'], 'https://nodejs.org/')); | | 239 | + | | 240 | + helpText = RegExpPrototypeSymbolReplace( | | 241 | + / \(currently set\)/g, | | 242 | + helpText, | | 243 | + style('dim', ' (currently set)'), | | 244 | + ); | | 245 | + | 231 | 246 | console.log(helpText); | 232 | 247 | } | 233 | 248 |
| |
Collapse file Copy file name to clipboard +64Lines changed: 64 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +const common = require('../common'); | | 4 | +const assert = require('assert'); | | 5 | +const { execFile } = require('child_process'); | | 6 | + | | 7 | +// eslint-disable-next-line no-control-regex | | 8 | +const ANSI_SGR_REGEX = new RegExp('\x1b\\[[0-9;]*m', 'g'); | | 9 | + | | 10 | +function stripAnsi(text) { | | 11 | + return text.replace(ANSI_SGR_REGEX, ''); | | 12 | +} | | 13 | + | | 14 | +// Test: FORCE_COLOR=1 produces styled output | | 15 | +{ | | 16 | + const env = { ...process.env, FORCE_COLOR: '1' }; | | 17 | + execFile(process.execPath, ['--help'], { env }, | | 18 | + common.mustSucceed((stdout) => { | | 19 | + assert.ok(stdout.includes('\x1b[1m'), 'bold for headers should be present'); | | 20 | + assert.ok(stdout.includes('\x1b[32m'), 'green for option names should be present'); | | 21 | + assert.ok(stdout.includes('\x1b[35m'), 'magenta for env var names should be present'); | | 22 | + assert.ok(stdout.includes('\x1b[34m'), 'blue for URL should be present'); | | 23 | + assert.ok(stdout.includes('\x1b[4m'), 'underline for URL should be present'); | | 24 | + })); | | 25 | +} | | 26 | + | | 27 | +// Test: NO_COLOR=1 produces plain output | | 28 | +{ | | 29 | + const env = { ...process.env, NO_COLOR: '1' }; | | 30 | + delete env.FORCE_COLOR; | | 31 | + execFile(process.execPath, ['--help'], { env }, | | 32 | + common.mustSucceed((stdout) => { | | 33 | + assert.ok(stripAnsi(stdout) === stdout, | | 34 | + 'no ANSI escape sequences should be present with NO_COLOR=1'); | | 35 | + })); | | 36 | +} | | 37 | + | | 38 | +// Test: piped (non-TTY, no FORCE_COLOR) produces plain output | | 39 | +{ | | 40 | + const env = { ...process.env }; | | 41 | + delete env.FORCE_COLOR; | | 42 | + delete env.NO_COLOR; | | 43 | + delete env.NODE_DISABLE_COLORS; | | 44 | + execFile(process.execPath, ['--help'], { env }, | | 45 | + common.mustSucceed((stdout) => { | | 46 | + assert.ok(stripAnsi(stdout) === stdout, | | 47 | + 'no ANSI escape sequences should be present when piped (non-TTY)'); | | 48 | + })); | | 49 | +} | | 50 | + | | 51 | +// Test: alignment preservation - stripped styled output matches plain output | | 52 | +{ | | 53 | + const envStyled = { ...process.env, FORCE_COLOR: '1' }; | | 54 | + const envPlain = { ...process.env, NO_COLOR: '1' }; | | 55 | + delete envPlain.FORCE_COLOR; | | 56 | + execFile(process.execPath, ['--help'], { env: envStyled }, | | 57 | + common.mustSucceed((styledStdout) => { | | 58 | + execFile(process.execPath, ['--help'], { env: envPlain }, | | 59 | + common.mustSucceed((plainStdout) => { | | 60 | + assert.ok(stripAnsi(styledStdout) === plainStdout, | | 61 | + 'stripped styled output should match plain output (alignment preservation)'); | | 62 | + })); | | 63 | + })); | | 64 | +} |
|