@@ -6,18 +6,23 @@ import { defineCommand } from 'citty'
66import { relative } from 'pathe'
77import { resolveDotenvFileNames } from '../utils/args'
88import { showBanner } from '../utils/banner'
9-
9+ import { BuildProgress } from '../utils/build-progress'
1010import { overrideEnv } from '../utils/env'
11+
1112import { ActionableError } from '../utils/errors'
1213import { formatDuration } from '../utils/formatting'
1314import { clearBuildDir } from '../utils/fs'
1415import { loadKit } from '../utils/kit'
1516import { acquireLock , acquireOutputLock , formatLockError } from '../utils/lockfile'
1617import { intro , logger , outro } from '../utils/logger'
1718import { resolveRootDir } from '../utils/paths'
19+ import { createPhaseReporter , formatPhaseBreakdown } from '../utils/phase-reporter'
1820import { startCpuProfile , stopCpuProfile } from '../utils/profile'
1921import { dotEnvArgs , envNameArgs , extendsArgs , logLevelArgs , profileArgs , rootDirArgs } from './_shared'
2022
23+ /** How often a phase repeats itself where there is no animated line. */
24+ const HEARTBEAT_INTERVAL = 5000
25+
2126export default defineCommand ( {
2227 meta : {
2328 name : 'build' ,
@@ -55,9 +60,27 @@ export default defineCommand({
5560 }
5661
5762 const releaseLocks : Array < ( ) => void > = [ ]
63+ const progress = new BuildProgress ( )
64+ let stopReporting = ( ) => { }
5865 try {
5966 intro ( styleText ( 'cyan' , 'Building Nuxt for production...' ) )
6067
68+ // The phase line owns a row of the terminal, which a silent build has no
69+ // business drawing on. Subscribed after the intro so the first phase is
70+ // reported below it rather than above.
71+ if ( ctx . args . logLevel !== 'silent' ) {
72+ const reporter = createPhaseReporter ( { heartbeat : HEARTBEAT_INTERVAL } )
73+ let unsubscribe : ( ( ) => void ) | undefined
74+ // Assigned before subscribing, because subscribing reports the phase in
75+ // flight straight away: a first write that fails, on a pipe that has
76+ // already been closed, must still leave the terminal recoverable.
77+ stopReporting = ( ) => {
78+ unsubscribe ?.( )
79+ reporter . stop ( )
80+ }
81+ unsubscribe = progress . onUpdate ( reporter . update )
82+ }
83+
6184 const kit = await loadKit ( cwd )
6285 const nuxt = await kit . loadNuxt ( {
6386 cwd,
@@ -85,6 +108,8 @@ export default defineCommand({
85108 } ,
86109 } )
87110
111+ progress . attachNuxt ( nuxt )
112+
88113 showBanner ( nuxt )
89114 await nuxt . ready ( )
90115
@@ -115,6 +140,14 @@ export default defineCommand({
115140
116141 await kit . buildNuxt ( nuxt )
117142
143+ stopReporting ( )
144+ progress . finish ( )
145+
146+ const breakdown = formatPhaseBreakdown ( progress . timings )
147+ if ( breakdown ) {
148+ logger . message ( styleText ( 'dim' , breakdown ) )
149+ }
150+
118151 if ( ctx . args . prerender ) {
119152 if ( ! nuxt . options . ssr ) {
120153 logger . warn ( `HTML content not prerendered because ${ styleText ( 'cyan' , 'ssr: false' ) } was set.` )
@@ -129,6 +162,7 @@ export default defineCommand({
129162 }
130163 }
131164 finally {
165+ stopReporting ( )
132166 for ( const release of releaseLocks . reverse ( ) ) {
133167 release ( )
134168 }
0 commit comments