@thegetty/quire-cli 1.0.0-rc.39 → 1.0.0-rc.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +14 -1
- package/package.json +1 -1
- package/src/commands/config.js +1 -1
- package/src/lib/commander/index.js +2 -0
- package/src/lib/commander/options.js +24 -0
- package/src/lib/commander/options.test.js +46 -1
- package/src/lib/logger/index.js +1 -1
- package/src/lib/logger/index.test.js +101 -0
- package/src/main.js +26 -0
package/bin/cli.js
CHANGED
|
@@ -20,7 +20,20 @@ process.removeAllListeners('warning')
|
|
|
20
20
|
process.env.QUIRE_LOG_LEVEL = config.get('logLevel') || 'info'
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Set NO_COLOR env var from config before importing CLI modules
|
|
24
|
+
*
|
|
25
|
+
* Chalk 5.x and ora read NO_COLOR at module load time.
|
|
26
|
+
* Setting it here ensures color is disabled before any styled imports.
|
|
27
|
+
* If NO_COLOR is already set in the shell environment, we do not override it.
|
|
28
|
+
*
|
|
29
|
+
* @see https://no-color.org/
|
|
30
|
+
*/
|
|
31
|
+
if (process.env.NO_COLOR === undefined && config.get('logUseColor') === false) {
|
|
32
|
+
process.env.NO_COLOR = '1'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Dynamic import ensures env vars are set before logger modules are loaded
|
|
24
37
|
*/
|
|
25
38
|
const { default: cli } = await import('#src/main.js')
|
|
26
39
|
|
package/package.json
CHANGED
package/src/commands/config.js
CHANGED
|
@@ -205,7 +205,7 @@ Examples:
|
|
|
205
205
|
const output = formatSettings(this.config.store, {
|
|
206
206
|
showInternal: options.debug,
|
|
207
207
|
configPath: this.config.path,
|
|
208
|
-
useColor: this.config.get('logUseColor'),
|
|
208
|
+
useColor: this.config.get('logUseColor') && !process.env.NO_COLOR,
|
|
209
209
|
})
|
|
210
210
|
this.logger.info(output)
|
|
211
211
|
}
|
|
@@ -124,6 +124,30 @@ export const debugOption = [
|
|
|
124
124
|
{ conflicts: ['quiet'] }
|
|
125
125
|
]
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Color options - enable/disable colored output
|
|
129
|
+
*
|
|
130
|
+
* Commander.js requires separate option definitions for --color and --no-color
|
|
131
|
+
* to support both the positive and negative forms. When --color has no default,
|
|
132
|
+
* the three-state semantics are:
|
|
133
|
+
*
|
|
134
|
+
* - `--color` → options.color = true (force color on)
|
|
135
|
+
* - `--no-color` → options.color = false (force color off)
|
|
136
|
+
* - (no flag) → options.color = undefined (falls back to env/config)
|
|
137
|
+
*
|
|
138
|
+
* Respects the NO_COLOR environment variable (https://no-color.org/).
|
|
139
|
+
* Respects config default: `quire settings set logUseColor false`
|
|
140
|
+
*
|
|
141
|
+
* @type {Array[]}
|
|
142
|
+
*/
|
|
143
|
+
export const colorOption = [
|
|
144
|
+
'--color', 'force colored output (overrides NO_COLOR env var)',
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
export const noColorOption = [
|
|
148
|
+
'--no-color', 'disable colored output',
|
|
149
|
+
]
|
|
150
|
+
|
|
127
151
|
/**
|
|
128
152
|
* Standard output mode options (quiet, verbose, debug, progress)
|
|
129
153
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import test from 'ava'
|
|
2
2
|
import { Command } from 'commander'
|
|
3
3
|
import { arrayToOption } from './index.js'
|
|
4
|
-
import { quietOption, verboseOption, debugOption } from './options.js'
|
|
4
|
+
import { colorOption, noColorOption, quietOption, verboseOption, debugOption } from './options.js'
|
|
5
5
|
|
|
6
6
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
7
|
// Integration tests for option conflicts
|
|
@@ -16,6 +16,8 @@ import { quietOption, verboseOption, debugOption } from './options.js'
|
|
|
16
16
|
function createTestProgram() {
|
|
17
17
|
const program = new Command()
|
|
18
18
|
program
|
|
19
|
+
.addOption(arrayToOption(colorOption))
|
|
20
|
+
.addOption(arrayToOption(noColorOption))
|
|
19
21
|
.addOption(arrayToOption(quietOption))
|
|
20
22
|
.addOption(arrayToOption(verboseOption))
|
|
21
23
|
.addOption(arrayToOption(debugOption))
|
|
@@ -107,3 +109,46 @@ test('options order does not affect conflict detection', (t) => {
|
|
|
107
109
|
t.throws(() => program1.parse(['node', 'test', '--quiet', '--verbose']))
|
|
108
110
|
t.throws(() => program2.parse(['node', 'test', '--verbose', '--quiet']))
|
|
109
111
|
})
|
|
112
|
+
|
|
113
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
114
|
+
// Color option tests
|
|
115
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
test('--no-color sets color to false', (t) => {
|
|
118
|
+
const program = createTestProgram()
|
|
119
|
+
program.parse(['node', 'test', '--no-color'])
|
|
120
|
+
t.is(program.opts().color, false)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('--color sets color to true', (t) => {
|
|
124
|
+
const program = createTestProgram()
|
|
125
|
+
program.parse(['node', 'test', '--color'])
|
|
126
|
+
t.is(program.opts().color, true)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('no color flag leaves color undefined', (t) => {
|
|
130
|
+
const program = createTestProgram()
|
|
131
|
+
program.parse(['node', 'test'])
|
|
132
|
+
t.is(program.opts().color, undefined)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('--no-color can be combined with --verbose', (t) => {
|
|
136
|
+
const program = createTestProgram()
|
|
137
|
+
t.notThrows(() => {
|
|
138
|
+
program.parse(['node', 'test', '--no-color', '--verbose'])
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('--no-color can be combined with --quiet', (t) => {
|
|
143
|
+
const program = createTestProgram()
|
|
144
|
+
t.notThrows(() => {
|
|
145
|
+
program.parse(['node', 'test', '--no-color', '--quiet'])
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('--no-color can be combined with --debug', (t) => {
|
|
150
|
+
const program = createTestProgram()
|
|
151
|
+
t.notThrows(() => {
|
|
152
|
+
program.parse(['node', 'test', '--no-color', '--debug'])
|
|
153
|
+
})
|
|
154
|
+
})
|
package/src/lib/logger/index.js
CHANGED
|
@@ -162,7 +162,7 @@ export default function createLogger(name = 'quire', level) {
|
|
|
162
162
|
const prefix = config.get('logPrefix')
|
|
163
163
|
const prefixStyle = config.get('logPrefixStyle')
|
|
164
164
|
const showLevel = config.get('logShowLevel')
|
|
165
|
-
const useColor = config.get('logUseColor')
|
|
165
|
+
const useColor = config.get('logUseColor') && !process.env.NO_COLOR
|
|
166
166
|
const colorMessages = config.get('logColorMessages')
|
|
167
167
|
|
|
168
168
|
const parts = []
|
|
@@ -475,3 +475,104 @@ test.serial('invalid env var value falls back to info', async (t) => {
|
|
|
475
475
|
}
|
|
476
476
|
}
|
|
477
477
|
})
|
|
478
|
+
|
|
479
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
480
|
+
// NO_COLOR environment variable tests
|
|
481
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
482
|
+
|
|
483
|
+
test.serial('logger disables color when NO_COLOR env var is set', async (t) => {
|
|
484
|
+
const { sandbox } = t.context
|
|
485
|
+
const consoleErrorStub = sandbox.stub(console, 'error')
|
|
486
|
+
const originalNoColor = process.env.NO_COLOR
|
|
487
|
+
|
|
488
|
+
try {
|
|
489
|
+
process.env.NO_COLOR = '1'
|
|
490
|
+
|
|
491
|
+
const mockConfig = {
|
|
492
|
+
get: sandbox.stub().callsFake((key) => ({
|
|
493
|
+
logPrefix: 'quire',
|
|
494
|
+
logPrefixStyle: 'bracket',
|
|
495
|
+
logShowLevel: true,
|
|
496
|
+
logUseColor: true, // Config says use color, but NO_COLOR overrides
|
|
497
|
+
logColorMessages: true,
|
|
498
|
+
})[key])
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const { default: createLoggerMocked } = await esmock('./index.js', {
|
|
502
|
+
'#lib/conf/config.js': { default: mockConfig }
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
const log = createLoggerMocked('test:nocolor', 'error')
|
|
506
|
+
log.error('Error message')
|
|
507
|
+
|
|
508
|
+
t.true(consoleErrorStub.calledOnce)
|
|
509
|
+
const output = consoleErrorStub.firstCall.args.join(' ')
|
|
510
|
+
// Should not contain ANSI escape sequences when NO_COLOR is set
|
|
511
|
+
t.false(/\u001b\[/.test(output), 'output should not contain ANSI escape codes')
|
|
512
|
+
// Should still contain the actual text
|
|
513
|
+
t.true(output.includes('[quire]'))
|
|
514
|
+
t.true(output.includes('ERROR'))
|
|
515
|
+
t.true(output.includes('Error message'))
|
|
516
|
+
} finally {
|
|
517
|
+
if (originalNoColor === undefined) {
|
|
518
|
+
delete process.env.NO_COLOR
|
|
519
|
+
} else {
|
|
520
|
+
process.env.NO_COLOR = originalNoColor
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
test.serial('logger uses color when NO_COLOR env var is not set and config enables color', async (t) => {
|
|
526
|
+
const { sandbox } = t.context
|
|
527
|
+
const consoleErrorStub = sandbox.stub(console, 'error')
|
|
528
|
+
const originalNoColor = process.env.NO_COLOR
|
|
529
|
+
|
|
530
|
+
try {
|
|
531
|
+
delete process.env.NO_COLOR
|
|
532
|
+
|
|
533
|
+
const mockConfig = {
|
|
534
|
+
get: sandbox.stub().callsFake((key) => ({
|
|
535
|
+
logPrefix: 'quire',
|
|
536
|
+
logPrefixStyle: 'bracket',
|
|
537
|
+
logShowLevel: true,
|
|
538
|
+
logUseColor: true,
|
|
539
|
+
logColorMessages: true,
|
|
540
|
+
})[key])
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Create a mock chalk that always applies ANSI styling regardless of TTY/env
|
|
544
|
+
const ansiRed = (s) => `\u001b[91m${s}\u001b[39m`
|
|
545
|
+
const ansiRedInverse = (s) => `\u001b[91m\u001b[7m${s}\u001b[27m\u001b[39m`
|
|
546
|
+
const ansiBold = (s) => `\u001b[1m${s}\u001b[22m`
|
|
547
|
+
const mockChalk = {
|
|
548
|
+
bold: ansiBold,
|
|
549
|
+
gray: (s) => s,
|
|
550
|
+
yellow: (s) => s,
|
|
551
|
+
magenta: (s) => s,
|
|
552
|
+
red: ansiRed,
|
|
553
|
+
redBright: { inverse: ansiRedInverse },
|
|
554
|
+
'yellow.inverse': (s) => s,
|
|
555
|
+
}
|
|
556
|
+
// Support chained properties
|
|
557
|
+
mockChalk.yellow.inverse = (s) => s
|
|
558
|
+
|
|
559
|
+
const { default: createLoggerMocked } = await esmock('./index.js', {
|
|
560
|
+
'#lib/conf/config.js': { default: mockConfig },
|
|
561
|
+
'chalk': { default: mockChalk }
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
const log = createLoggerMocked('test:withcolor', 'error')
|
|
565
|
+
log.error('Error message')
|
|
566
|
+
|
|
567
|
+
t.true(consoleErrorStub.calledOnce)
|
|
568
|
+
const output = consoleErrorStub.firstCall.args.join(' ')
|
|
569
|
+
// Should contain ANSI escape sequences when color is enabled and NO_COLOR not set
|
|
570
|
+
t.true(/\u001b\[/.test(output), 'output should contain ANSI escape codes')
|
|
571
|
+
} finally {
|
|
572
|
+
if (originalNoColor === undefined) {
|
|
573
|
+
delete process.env.NO_COLOR
|
|
574
|
+
} else {
|
|
575
|
+
process.env.NO_COLOR = originalNoColor
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
})
|
package/src/main.js
CHANGED
|
@@ -2,6 +2,8 @@ import { Command, Argument, Option } from 'commander'
|
|
|
2
2
|
import {
|
|
3
3
|
arrayToArgument,
|
|
4
4
|
arrayToOption,
|
|
5
|
+
colorOption,
|
|
6
|
+
noColorOption,
|
|
5
7
|
quietOption,
|
|
6
8
|
verboseOption,
|
|
7
9
|
debugOption
|
|
@@ -34,12 +36,20 @@ Output Modes:
|
|
|
34
36
|
|
|
35
37
|
Set defaults: quire settings set verbose true
|
|
36
38
|
|
|
39
|
+
Color Output:
|
|
40
|
+
--no-color Disable colored output
|
|
41
|
+
--color Force colored output (overrides NO_COLOR env var)
|
|
42
|
+
|
|
43
|
+
Respects NO_COLOR environment variable (https://no-color.org/)
|
|
44
|
+
Set default: quire settings set logUseColor false
|
|
45
|
+
|
|
37
46
|
Paging:
|
|
38
47
|
--no-pager Disable paging for long output
|
|
39
48
|
NO_PAGER=1 Disable paging via environment variable
|
|
40
49
|
PAGER=cat Traditional Unix alternative (passes output through)
|
|
41
50
|
|
|
42
51
|
Environment Variables:
|
|
52
|
+
NO_COLOR Disable colored output (https://no-color.org/)
|
|
43
53
|
NO_PAGER=1 Disable paging for long output
|
|
44
54
|
PAGER=<program> Set pager program (default: less). Use PAGER=cat to disable
|
|
45
55
|
DEBUG=quire:* Enable debug output for all modules
|
|
@@ -50,6 +60,8 @@ Examples:
|
|
|
50
60
|
$ quire build Build the publication
|
|
51
61
|
$ quire build --verbose Build with detailed progress
|
|
52
62
|
$ quire build --debug Build with debug output
|
|
63
|
+
$ quire build --no-color Build without colored output
|
|
64
|
+
$ NO_COLOR=1 quire build Build without colored output
|
|
53
65
|
$ DEBUG=quire:* quire pdf Generate PDF with debug output
|
|
54
66
|
`
|
|
55
67
|
|
|
@@ -66,6 +78,8 @@ program
|
|
|
66
78
|
.name('quire')
|
|
67
79
|
.description('Quire command-line interface')
|
|
68
80
|
.version(version, '-V, --version', 'output quire version number')
|
|
81
|
+
.addOption(arrayToOption(colorOption))
|
|
82
|
+
.addOption(arrayToOption(noColorOption))
|
|
69
83
|
.addOption(arrayToOption(quietOption))
|
|
70
84
|
.addOption(arrayToOption(verboseOption))
|
|
71
85
|
.addOption(arrayToOption(debugOption))
|
|
@@ -88,6 +102,8 @@ program
|
|
|
88
102
|
* - --quiet: Suppress progress spinners (for CI/scripts)
|
|
89
103
|
* - --verbose: Show detailed progress (paths, timing, steps)
|
|
90
104
|
* - --debug: Enable DEBUG namespace + tool debug modes (for developers)
|
|
105
|
+
* - --no-color: Disable colored output (sets NO_COLOR env var)
|
|
106
|
+
* - --color: Force colored output (overrides NO_COLOR env var)
|
|
91
107
|
*
|
|
92
108
|
* These global options are passed through to commands via opts()
|
|
93
109
|
* and should be merged with command-level options.
|
|
@@ -95,6 +111,16 @@ program
|
|
|
95
111
|
program.hook('preAction', (thisCommand) => {
|
|
96
112
|
const opts = thisCommand.opts()
|
|
97
113
|
|
|
114
|
+
// Handle --no-color / --color flag
|
|
115
|
+
// Sets NO_COLOR env var for chalk, ora, and logger to read
|
|
116
|
+
if (opts.color === false) {
|
|
117
|
+
process.env.NO_COLOR = '1'
|
|
118
|
+
delete process.env.FORCE_COLOR
|
|
119
|
+
} else if (opts.color === true) {
|
|
120
|
+
delete process.env.NO_COLOR
|
|
121
|
+
process.env.FORCE_COLOR = '1'
|
|
122
|
+
}
|
|
123
|
+
|
|
98
124
|
// --debug or config.debug enables the quire:* DEBUG namespace for internal logging
|
|
99
125
|
// CLI flag takes precedence, then config setting
|
|
100
126
|
if (opts.debug ?? config.get('debug')) {
|