Skip to content

Commit 39420c9

Browse files
committed
fix(cli): drop clack gutter framing from redirected output
1 parent 3904632 commit 39420c9

9 files changed

Lines changed: 144 additions & 19 deletions

File tree

‎packages/nuxt-cli/src/commands/add-template.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ import { promises as fsp } from 'node:fs'
44
importprocessfrom'node:process'
55
import{styleText}from'node:util'
66

7-
import{intro,outro}from'@clack/prompts'
87
import{defineCommand}from'citty'
98
import{dirname,isAbsolute,relative,resolve}from'pathe'
109

1110
import{loadKit}from'../utils/kit'
12-
import{logger}from'../utils/logger'
11+
import{intro,logger,outro}from'../utils/logger'
1312
import{relativeToProcess}from'../utils/paths'
1413
import{templates}from'../utils/templates/index'
1514
import{httpMethods,modes}from'../utils/templates/modifiers'

‎packages/nuxt-cli/src/commands/analyze.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { promises as fsp } from 'node:fs'
44
importprocessfrom'node:process'
55

66
import{styleText}from'node:util'
7-
import{intro,note,outro,taskLog}from'@clack/prompts'
7+
import{note,taskLog}from'@clack/prompts'
88
import{defineCommand}from'citty'
99
import{defu}from'defu'
1010
import{join,relative,resolve}from'pathe'
@@ -16,7 +16,7 @@ import { ActionableError } from '../utils/errors'
1616
import{clearDir}from'../utils/fs'
1717
import{loadKit}from'../utils/kit'
1818
import{acquireLock,acquireOutputLock,formatLockError}from'../utils/lockfile'
19-
import{logger}from'../utils/logger'
19+
import{intro,logger,outro}from'../utils/logger'
2020
import{relativeToProcess,resolveRootDir}from'../utils/paths'
2121
import{dotEnvArgs,extendsArgs,logLevelArgs,rootDirArgs}from'./_shared'
2222

‎packages/nuxt-cli/src/commands/build.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
importprocessfrom'node:process'
22

33
import{styleText}from'node:util'
4-
import{intro,outro}from'@clack/prompts'
54
import{defineCommand}from'citty'
65

76
import{relative}from'pathe'
@@ -14,7 +13,7 @@ import { formatDuration } from '../utils/formatting'
1413
import{clearBuildDir}from'../utils/fs'
1514
import{loadKit}from'../utils/kit'
1615
import{acquireLock,acquireOutputLock,formatLockError}from'../utils/lockfile'
17-
import{logger}from'../utils/logger'
16+
import{intro,logger,outro}from'../utils/logger'
1817
import{resolveRootDir}from'../utils/paths'
1918
import{startCpuProfile,stopCpuProfile}from'../utils/profile'
2019
import{dotEnvArgs,envNameArgs,extendsArgs,logLevelArgs,profileArgs,rootDirArgs}from'./_shared'

‎packages/nuxt-cli/src/commands/preview.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ import { dirname } from 'node:path'
33
importprocessfrom'node:process'
44

55
import{styleText}from'node:util'
6-
import{box,outro}from'@clack/prompts'
6+
import{box}from'@clack/prompts'
77
import{tokenizeArgs}from'args-tokenizer'
88
import{defineCommand}from'citty'
99
import{resolve}from'pathe'
1010
import{x}from'tinyexec'
1111

1212
import{resolveDotenvFileNames}from'../utils/args'
1313
import{loadKit}from'../utils/kit'
14-
import{logger}from'../utils/logger'
14+
import{logger,outro}from'../utils/logger'
1515
import{withPrependedPath}from'../utils/path-env'
1616
import{relativeToProcess,resolveRootDir}from'../utils/paths'
1717
import{dotEnvArgs,envNameArgs,extendsArgs,logLevelArgs,rootDirArgs}from'./_shared'

‎packages/nuxt-cli/src/commands/upgrade.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { existsSync } from 'node:fs'
77
importprocessfrom'node:process'
88

99
import{styleText}from'node:util'
10-
import{cancel,intro,isCancel,note,outro,select,spinner}from'@clack/prompts'
10+
import{cancel,isCancel,note,select,spinner}from'@clack/prompts'
1111
import{defineCommand}from'citty'
1212
import{detectPackageManager}from'nypm'
1313
import{dirname,relative,resolve}from'pathe'
@@ -16,7 +16,7 @@ import { findWorkspaceDir, readPackageJSON } from 'pkg-types'
1616
import{resolveCatalogEntry,updateCatalogEntries}from'../utils/catalog'
1717
import{createInstallLog,runDedupe,runInstall,takeUnreportedIgnoredBuilds}from'../utils/install'
1818
import{loadKit}from'../utils/kit'
19-
import{logger}from'../utils/logger'
19+
import{intro,logger,outro}from'../utils/logger'
2020
import{cleanupNuxtDirs,nuxtVersionToGitIdentifier}from'../utils/nuxt'
2121
import{getPackageManagerVersion}from'../utils/packageManagers'
2222
import{relativeToProcess,resolveRootDir}from'../utils/paths'

‎packages/nuxt-cli/src/utils/logger.ts‎

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,39 @@
1+
importprocessfrom'node:process'
12
import{styleText}from'node:util'
2-
import{log,S_INFO}from'@clack/prompts'
3+
import{introasclackIntro,outroasclackOutro,log,S_ERROR,S_INFO,S_STEP_SUBMIT,S_SUCCESS,S_WARN}from'@clack/prompts'
34
import{createDebug}from'obug'
5+
import{isCI}from'std-env'
46

57
import{blankLineBefore,writeDirect}from'./stdout'
68

79
typeLoggerImpl=Pick<typeoflog,'info'|'warn'|'error'|'success'|'step'|'message'>
810

9-
letimpl: LoggerImpl=log
11+
typeLineColor='blue'|'yellow'|'red'|'green'
12+
13+
/**
14+
* A log line with no clack framing: the `│` gutter connects one prompt to the
15+
* next on screen, and in a redirected log it is a bare column of punctuation
16+
* between every line of real output.
17+
*/
18+
functionplainLine(symbol: string,color: LineColor,message?: string): void{
19+
constbody=String(message??'').replace(/\n/g,'\n ')
20+
writeDirect(`${styleText(color,symbol)}${body}\n`)
21+
}
22+
23+
constplain: LoggerImpl={
24+
info: message=>plainLine(S_INFO,'blue',message),
25+
warn: message=>plainLine(S_WARN,'yellow',message),
26+
error: message=>plainLine(S_ERROR,'red',message),
27+
success: message=>plainLine(S_SUCCESS,'green',message),
28+
step: message=>plainLine(S_STEP_SUBMIT,'green',message),
29+
message: message=>writeDirect(`${String(message??'')}\n`),
30+
}
31+
32+
functiondefaultImpl(): LoggerImpl{
33+
return!process.stdout.isTTY||isCI ? plain : log
34+
}
35+
36+
letimpl: LoggerImpl|undefined
1037
letdepth=0
1138

1239
/**
@@ -36,16 +63,37 @@ function emit<T>(write: () => T): T {
3663
* guideline, which reads as a stray artefact next to a persistent footer.
3764
*/
3865
exportfunctionsetLoggerImpl(next?: LoggerImpl): void{
39-
impl=next??log
66+
impl=next
4067
}
4168

4269
exportconstlogger: LoggerImpl={
43-
info: message=>emit(()=>impl.info(message)),
44-
warn: message=>emit(()=>impl.warn(message)),
45-
error: message=>emit(()=>impl.error(message)),
46-
success: message=>emit(()=>impl.success(message)),
47-
step: message=>emit(()=>impl.step(message)),
48-
message: (message,options)=>emit(()=>impl.message(message,options)),
70+
info: message=>emit(()=>(impl??defaultImpl()).info(message)),
71+
warn: message=>emit(()=>(impl??defaultImpl()).warn(message)),
72+
error: message=>emit(()=>(impl??defaultImpl()).error(message)),
73+
success: message=>emit(()=>(impl??defaultImpl()).success(message)),
74+
step: message=>emit(()=>(impl??defaultImpl()).step(message)),
75+
message: (message,options)=>emit(()=>(impl??defaultImpl()).message(message,options)),
76+
}
77+
78+
/**
79+
* Open a command's output. Falls back to a bare headline where clack's opening
80+
* corner would only introduce a gutter nothing draws against.
81+
*/
82+
exportfunctionintro(message: string): void{
83+
if(!process.stdout.isTTY||isCI){
84+
writeDirect(`${message}\n`)
85+
return
86+
}
87+
clackIntro(message)
88+
}
89+
90+
/** {@link intro}, for the line a command finishes on. */
91+
exportfunctionoutro(message: string): void{
92+
if(!process.stdout.isTTY||isCI){
93+
writeDirect(`${styleText('green',S_SUCCESS)}${message}\n`)
94+
return
95+
}
96+
clackOutro(message)
4997
}
5098

5199
exportconstdebug=createDebug('nuxi')

‎packages/nuxt-cli/test/unit/commands/build.spec.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ vi.mock('../../../src/utils/lockfile', () => ({
3737
}))
3838
vi.mock('../../../src/utils/logger',()=>({
3939
logger: {error: vi.fn(),info: vi.fn(),warn: vi.fn()},
40+
intro: vi.fn(),
41+
outro: vi.fn(),
4042
}))
4143
vi.mock('../../../src/utils/profile',()=>({
4244
startCpuProfile: mocks.startCpuProfile,

‎packages/nuxt-cli/test/unit/utils/banner.spec.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import { describe, expect, it, vi } from 'vitest'
44

55
import{render,screen}from'../../utils/terminal'
66

7+
vi.mock('std-env',asyncimportOriginal=>({
8+
...awaitimportOriginal<typeofimport('std-env')>(),
9+
isCI: false,
10+
}))
11+
712
constVERSIONS: Record<string,string>={
813
'webpack': '5.99.0',
914
'@rspack/core': '1.3.0',
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import{afterEach,describe,expect,it,vi}from'vitest'
2+
3+
import{stripAnsi}from'../../../src/dev/tui/width'
4+
import{logger}from'../../../src/utils/logger'
5+
6+
constenvironment=vi.hoisted(()=>({isCI: false}))
7+
8+
vi.mock('std-env',asyncimportOriginal=>({
9+
...awaitimportOriginal<typeofimport('std-env')>(),
10+
getisCI(){
11+
returnenvironment.isCI
12+
},
13+
}))
14+
15+
describe('logger',()=>{
16+
constrestores: Array<()=>void>=[]
17+
18+
afterEach(()=>{
19+
restores.splice(0).forEach(restore=>restore())
20+
environment.isCI=false
21+
vi.restoreAllMocks()
22+
})
23+
24+
functioncapture(isTTY: boolean): {written: ()=>string}{
25+
constoriginal=process.stdout.isTTY
26+
Object.defineProperty(process.stdout,'isTTY',{value: isTTY,configurable: true})
27+
restores.push(()=>Object.defineProperty(process.stdout,'isTTY',{value: original,configurable: true}))
28+
29+
constchunks: string[]=[]
30+
constwrite=vi.spyOn(process.stdout,'write').mockImplementation((chunk: unknown)=>{
31+
chunks.push(String(chunk))
32+
returntrue
33+
})
34+
restores.push(()=>write.mockRestore())
35+
36+
return{written: ()=>stripAnsi(chunks.join(''))}
37+
}
38+
39+
it('should leave no clack gutter in redirected output',()=>{
40+
const{ written }=capture(false)
41+
42+
logger.info('Bundling app')
43+
logger.warn('Slow')
44+
45+
expect(written()).toBe('● Bundling app\n▲ Slow\n')
46+
})
47+
48+
it('should keep clack framing in a terminal',()=>{
49+
const{ written }=capture(true)
50+
51+
logger.info('Bundling app')
52+
53+
expect(written()).toContain('│\n')
54+
})
55+
56+
it('should leave no clack gutter in CI, where nothing draws against it',()=>{
57+
environment.isCI=true
58+
const{ written }=capture(true)
59+
60+
logger.info('Bundling app')
61+
62+
expect(written()).toBe('● Bundling app\n')
63+
})
64+
65+
it('should indent a continuation line under its symbol',()=>{
66+
const{ written }=capture(false)
67+
68+
logger.info('Ready in 2.4s\nconfig 320ms')
69+
70+
expect(written()).toBe('● Ready in 2.4s\n config 320ms\n')
71+
})
72+
})

0 commit comments

Comments
 (0)