Skip to content

Commit f6ecc61

Browse files
committed
refactor(cli): simplify and consolidate CLI codebase
- Remove dead code (ServerManager, spawnWithNodeVersion, cancel methods) - Consolidate constants and utilities to single sources - Add --verbose flag support across all commands - Use local gitbook-cli install in .gitbook/cli/
1 parent 3495ec0 commit f6ecc61

File tree

14 files changed

+369
-722
lines changed

14 files changed

+369
-722
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ lychee*
1313
_book/
1414
_book_temp/
1515
node_modules/
16-
package-lock.json
16+
package-lock.json
17+
.gitbook/cli/

book.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2+
"gitbook": "3.2.3",
23
"plugins": ["local"]
34
}

gitbook-plugins/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@
2525
},
2626
"devDependencies": {
2727
"@types/node": "^20.19.25",
28-
"typescript": "^5.9.3"
28+
"typescript": "^4.9.5"
2929
}
3030
}

gitbook-plugins/src/cli/builder.ts

Lines changed: 40 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,156 +1,76 @@
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';
33
import { 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

78
type NvmPath = Awaited<ReturnType<typeof setupNodeVersion>>['nvmPath'];
8-
export type BuildState = 'idle' | 'building' | 'cancelling';
99

1010
export 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 = /generation finished with success/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 || /generation finished with success/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-
/Error:|error:|TypeError|ENOENT|Template render error/.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

Comments
 (0)