1- import { spawn , ChildProcess } from 'child_process' ;
2- import { existsSync , rmSync , renameSync } from 'fs' ;
1+ import { spawn } from 'child_process' ;
2+ import { existsSync , renameSync } from 'fs' ;
33import { log , spin } from './log' ;
4- import { getRequiredNodeVersion , setupNodeVersion } from './node-version' ;
5- import { CONFIG , getBookPath , getTempBookPath } from './constants' ;
4+ import { setupNodeVersion , createNvmCommand } from './node-version' ;
5+ import { getGitbookBin } from './gitbook' ;
6+ import { CONFIG , getBookPath , getTempBookPath , rmRecursive } from './constants' ;
67
78type NvmPath = Awaited < ReturnType < typeof setupNodeVersion > > [ 'nvmPath' ] ;
8- export type BuildState = 'idle' | 'building' | 'cancelling' ;
99
1010export class Builder {
11- private projectRoot : string ;
12- private nvmPath : NvmPath ;
13- private verbose : boolean ;
14- private buildProcess : ChildProcess | null = null ;
15- private buildProcessPid : number | null = null ;
16- private state : BuildState = 'idle' ;
11+ constructor (
12+ private projectRoot : string ,
13+ private nvmPath : NvmPath ,
14+ private verbose = false
15+ ) { }
1716
18- constructor ( projectRoot : string , nvmPath : NvmPath , verbose : boolean = false ) {
19- this . projectRoot = projectRoot ;
20- this . nvmPath = nvmPath ;
21- this . verbose = verbose ;
22- }
23-
24- public get currentState ( ) : BuildState {
25- return this . state ;
26- }
17+ async build ( files : string [ ] = [ ] ) : Promise < boolean > {
18+ const tempPath = getTempBookPath ( this . projectRoot ) ;
19+ const bookPath = getBookPath ( this . projectRoot ) ;
20+ this . cleanup ( tempPath ) ;
2721
28- public async build ( files : string [ ] = [ ] ) : Promise < boolean > {
29- this . state = 'building' ;
30- const tempBookPath = getTempBookPath ( this . projectRoot ) ;
31- this . cleanupTemp ( ) ;
22+ log . debug ( `Build output: ${ bookPath } ` , this . verbose ) ;
23+ log . debug ( `Temp output: ${ tempPath } ` , this . verbose ) ;
3224
25+ const isRebuild = files . length > 0 ;
3326 const filesMsg = this . formatFiles ( files ) ;
34- spin . start ( filesMsg ? `Rebuilding (${ filesMsg } )...` : 'Rebuilding ...' ) ;
27+ spin . start ( isRebuild ? `Rebuilding (${ filesMsg } )...` : 'Building ...' ) ;
3528
3629 return new Promise ( ( resolve ) => {
37- const buildCmd = this . createNvmCommand ( `gitbook build . "${ tempBookPath } " 2>&1` ) ;
38-
39- this . buildProcess = spawn ( buildCmd , {
40- stdio : 'pipe' ,
41- shell : '/bin/bash' ,
42- cwd : this . projectRoot ,
43- detached : true ,
44- } ) ;
45-
46- this . buildProcessPid = this . buildProcess . pid ? - this . buildProcess . pid : null ;
30+ const cmd = createNvmCommand ( `"${ getGitbookBin ( ) } " build . "${ tempPath } " 2>&1` , this . nvmPath ) ;
31+ log . debug ( `Build command: ${ cmd } ` , this . verbose ) ;
4732
33+ const proc = spawn ( cmd , { stdio : 'pipe' , shell : '/bin/bash' , cwd : this . projectRoot , detached : true } ) ;
4834 let output = '' ;
49- this . buildProcess . stdout ?. on ( 'data' , ( data ) => { output += data . toString ( ) ; } ) ;
50- this . buildProcess . stderr ?. on ( 'data' , ( data ) => { output += data . toString ( ) ; } ) ;
5135
52- this . buildProcess . on ( 'close' , ( code , signal ) => {
53- const wasCancelled = this . state === 'cancelling' || signal != null ;
54- this . cleanupProcess ( ) ;
36+ proc . stdout ?. on ( 'data' , ( d ) => { output += d ; } ) ;
37+ proc . stderr ?. on ( 'data' , ( d ) => { output += d ; } ) ;
5538
56- if ( wasCancelled ) {
57- this . cleanupTemp ( ) ;
58- resolve ( false ) ;
59- return ;
60- }
39+ proc . on ( 'close' , ( code ) => {
40+ if ( this . verbose && output ) console . log ( output ) ;
6141
62- const hasSuccess = / g e n e r a t i o n f i n i s h e d w i t h s u c c e s s / i. test ( output ) ;
63- if ( code === 0 || hasSuccess ) {
64- if ( this . swapBuildOutput ( ) ) {
65- spin . succeed ( 'Rebuild complete' ) ;
66- resolve ( true ) ;
67- } else {
68- spin . fail ( 'Build output missing' ) ;
69- resolve ( false ) ;
70- }
42+ const success = code === 0 || / g e n e r a t i o n f i n i s h e d w i t h s u c c e s s / i. test ( output ) ;
43+ if ( success && this . swap ( tempPath , bookPath ) ) {
44+ spin . succeed ( isRebuild ? 'Rebuild complete' : 'Build complete' ) ;
45+ resolve ( true ) ;
7146 } else {
72- spin . fail ( 'Rebuild failed' ) ;
73- this . logErrors ( output ) ;
74- this . cleanupTemp ( ) ;
47+ spin . fail ( isRebuild ? 'Rebuild failed' : 'Build failed') ;
48+ if ( output ) console . log ( output ) ;
49+ this . cleanup ( tempPath ) ;
7550 resolve ( false ) ;
7651 }
7752 } ) ;
7853
79- this . buildProcess . on ( 'error' , ( ) => {
80- this . cleanupProcess ( ) ;
54+ proc . on ( 'error' , ( ) => {
8155 spin . fail ( 'Build process error' ) ;
82- this . cleanupTemp ( ) ;
56+ this . cleanup ( tempPath ) ;
8357 resolve ( false ) ;
8458 } ) ;
8559 } ) ;
8660 }
8761
88- public cancel ( ) : void {
89- if ( ! this . buildProcess || this . state !== 'building' ) return ;
90- this . state = 'cancelling' ;
91-
92- if ( this . buildProcessPid ) {
93- try {
94- process . kill ( this . buildProcessPid , 'SIGTERM' ) ;
95- } catch {
96- // Process might already be dead
97- }
98- } else if ( this . buildProcess . pid ) {
99- this . buildProcess . kill ( 'SIGTERM' ) ;
100- }
101-
102- setTimeout ( ( ) => {
103- if ( this . buildProcessPid ) {
104- try {
105- process . kill ( this . buildProcessPid , 'SIGKILL' ) ;
106- } catch {
107- // Process might already be dead
108- }
109- } else if ( this . buildProcess ?. pid ) {
110- this . buildProcess . kill ( 'SIGKILL' ) ;
111- }
112- } , 1000 ) ;
62+ private cleanup ( path : string ) {
63+ try { if ( existsSync ( path ) ) rmRecursive ( path ) ; } catch { /* ignore */ }
11364 }
11465
115- private cleanupProcess ( ) : void {
116- this . buildProcess = null ;
117- this . buildProcessPid = null ;
118- this . state = 'idle' ;
119- }
120-
121- private createNvmCommand ( command : string ) : string {
122- if ( ! this . nvmPath ) return command ;
123- const version = getRequiredNodeVersion ( ) ;
124- return `export NVM_DIR="${ this . nvmPath . dir } " && . "${ this . nvmPath . script } " && nvm use ${ version } 2>/dev/null && ${ command } ` ;
125- }
126-
127- private cleanupTemp ( ) : void {
128- const tempPath = getTempBookPath ( this . projectRoot ) ;
129- try {
130- if ( existsSync ( tempPath ) ) {
131- rmSync ( tempPath , { recursive : true , force : true } ) ;
132- }
133- } catch {
134- // Ignore cleanup errors
135- }
136- }
137-
138- private swapBuildOutput ( ) : boolean {
139- const bookPath = getBookPath ( this . projectRoot ) ;
140- const tempBookPath = getTempBookPath ( this . projectRoot ) ;
141-
66+ private swap ( tempPath : string , bookPath : string ) : boolean {
14267 try {
143- if ( existsSync ( bookPath ) ) {
144- rmSync ( bookPath , { recursive : true , force : true } ) ;
145- }
146- if ( existsSync ( tempBookPath ) ) {
147- renameSync ( tempBookPath , bookPath ) ;
148- return true ;
149- }
68+ if ( existsSync ( bookPath ) ) rmRecursive ( bookPath ) ;
69+ if ( existsSync ( tempPath ) ) { renameSync ( tempPath , bookPath ) ; return true ; }
15070 return false ;
15171 } catch ( err ) {
15272 log . error ( `Failed to swap build output: ${ err } ` ) ;
153- this . cleanupTemp ( ) ;
73+ this . cleanup ( tempPath ) ;
15474 return false ;
15575 }
15676 }
@@ -160,17 +80,4 @@ export class Builder {
16080 if ( files . length <= CONFIG . MAX_FILES_TO_SHOW ) return files . join ( ', ' ) ;
16181 return `${ files . slice ( 0 , CONFIG . MAX_FILES_TO_SHOW ) . join ( ', ' ) } +${ files . length - CONFIG . MAX_FILES_TO_SHOW } more` ;
16282 }
163-
164- private logErrors ( output : string ) : void {
165- const errorLines = output . split ( '\n' ) . filter ( line =>
166- / E r r o r : | e r r o r : | T y p e E r r o r | E N O E N T | T e m p l a t e r e n d e r e r r o r / . test ( line )
167- ) ;
168- if ( errorLines . length > 0 ) {
169- log . error ( errorLines . join ( '\n' ) ) ;
170- } else {
171- log . error ( 'See console for details' ) ;
172- console . log ( output . slice ( - 500 ) ) ;
173- }
174- }
17583}
176-
0 commit comments