honkit 6.1.5 → 6.1.7

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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=build-integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-integration.test.d.ts","sourceRoot":"","sources":["../../../src/cli/__tests__/build-integration.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const path_1 = __importDefault(require("path"));
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const bin_1 = require("../../bin");
10
+ const internal_test_utils_1 = require("@honkit/internal-test-utils");
11
+ /**
12
+ * Integration tests for honkit build output
13
+ *
14
+ * These tests use bin.run to execute the CLI and verify:
15
+ * 1. New files added to SUMMARY.md are correctly generated
16
+ * 2. Assets not in SUMMARY.md are copied correctly
17
+ * 3. Output structure matches expected snapshot
18
+ *
19
+ * This is a black-box test that only checks input (book files) and output (generated files).
20
+ */
21
+ jest.setTimeout(60000);
22
+ describe("build integration", () => {
23
+ let tempDir;
24
+ let outputDir;
25
+ beforeEach(() => {
26
+ tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), "honkit-build-test-"));
27
+ outputDir = path_1.default.join(tempDir, "_book");
28
+ });
29
+ afterEach(() => {
30
+ if (tempDir && fs_1.default.existsSync(tempDir)) {
31
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
32
+ }
33
+ });
34
+ function createBookStructure() {
35
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, "README.md"), `# Test Book
36
+
37
+ Welcome to the test book.
38
+ `);
39
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, "SUMMARY.md"), `# Summary
40
+
41
+ * [Introduction](README.md)
42
+ `);
43
+ }
44
+ async function buildBook() {
45
+ await (0, bin_1.run)([process.argv[0], "honkit", "build", tempDir, outputDir, "--reload"]);
46
+ }
47
+ function getOutputFiles() {
48
+ if (!fs_1.default.existsSync(outputDir))
49
+ return [];
50
+ const files = [];
51
+ const walk = (dir, prefix = "") => {
52
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
53
+ for (const entry of entries) {
54
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
55
+ if (entry.isDirectory()) {
56
+ walk(path_1.default.join(dir, entry.name), relativePath);
57
+ }
58
+ else {
59
+ files.push(relativePath);
60
+ }
61
+ }
62
+ };
63
+ walk(outputDir);
64
+ return files.sort();
65
+ }
66
+ describe("build output with bin.run", () => {
67
+ it("should generate index.html for README.md", async () => {
68
+ createBookStructure();
69
+ await buildBook();
70
+ const files = getOutputFiles();
71
+ expect(files).toContain("index.html");
72
+ });
73
+ it("should generate chapter page when added to SUMMARY.md", async () => {
74
+ createBookStructure();
75
+ // Add chapter
76
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, "chapter1.md"), `# Chapter 1
77
+
78
+ This is chapter 1.
79
+ `);
80
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, "SUMMARY.md"), `# Summary
81
+
82
+ * [Introduction](README.md)
83
+ * [Chapter 1](chapter1.md)
84
+ `);
85
+ await buildBook();
86
+ const files = getOutputFiles();
87
+ expect(files).toContain("index.html");
88
+ expect(files).toContain("chapter1.html");
89
+ });
90
+ it("should copy asset file not in SUMMARY.md", async () => {
91
+ createBookStructure();
92
+ // Add asset file (not in SUMMARY)
93
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, "notes.md"), `# Notes
94
+
95
+ These are my notes.
96
+ `);
97
+ await buildBook();
98
+ const files = getOutputFiles();
99
+ expect(files).toContain("index.html");
100
+ expect(files).toContain("notes.md"); // Copied as asset
101
+ });
102
+ it("should match expected output structure", async () => {
103
+ createBookStructure();
104
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, "chapter1.md"), `# Chapter 1
105
+
106
+ Content of chapter 1.
107
+ `);
108
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, "SUMMARY.md"), `# Summary
109
+
110
+ * [Introduction](README.md)
111
+ * [Chapter 1](chapter1.md)
112
+ `);
113
+ await buildBook();
114
+ const files = getOutputFiles();
115
+ // Filter out gitbook files and search index for cleaner snapshot
116
+ const relevantFiles = files.filter((f) => !f.startsWith("gitbook/") && !f.includes("search_index") && !f.includes("lunr"));
117
+ expect(relevantFiles).toMatchSnapshot("output-structure");
118
+ });
119
+ it("should generate correct HTML content", async () => {
120
+ createBookStructure();
121
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, "chapter1.md"), `# Chapter 1
122
+
123
+ Content of chapter 1.
124
+ `);
125
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, "SUMMARY.md"), `# Summary
126
+
127
+ * [Introduction](README.md)
128
+ * [Chapter 1](chapter1.md)
129
+ `);
130
+ await buildBook();
131
+ // Use iterateDirectoryContents like snapshot-honkit.ts
132
+ const maskContent = (content) => {
133
+ return content
134
+ .replace(/gitbook\.page\.hasChanged\(.*\);/g, "")
135
+ .replace(/<meta name="generator" content="HonKit .*">/g, "")
136
+ .replace(tempDir, "<TEMP_DIR>");
137
+ };
138
+ for await (const item of (0, internal_test_utils_1.iterateDirectoryContents)({
139
+ baseDir: outputDir,
140
+ allowExtensions: [".html"],
141
+ maskContent
142
+ })) {
143
+ // Only snapshot key pages, not all gitbook assets
144
+ if (item.filePath === "index.html" || item.filePath === "chapter1.html") {
145
+ expect(item).toMatchSnapshot(item.filePath);
146
+ }
147
+ }
148
+ });
149
+ });
150
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=getOutputFolder.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getOutputFolder.test.d.ts","sourceRoot":"","sources":["../../../src/cli/__tests__/getOutputFolder.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const path_1 = __importDefault(require("path"));
7
+ const getOutputFolder_1 = __importDefault(require("../getOutputFolder"));
8
+ describe("getOutputFolder", () => {
9
+ // Use platform-appropriate absolute paths for testing
10
+ const bookRoot = path_1.default.resolve("/book/root");
11
+ const absoluteOutput = path_1.default.resolve("/absolute/output");
12
+ describe("relative output path", () => {
13
+ it("should resolve relative to book root", () => {
14
+ const result = (0, getOutputFolder_1.default)([bookRoot, "./output"]);
15
+ expect(result).toBe(path_1.default.join(bookRoot, "output"));
16
+ });
17
+ it("should resolve relative path without ./ prefix", () => {
18
+ const result = (0, getOutputFolder_1.default)([bookRoot, "dist"]);
19
+ expect(result).toBe(path_1.default.join(bookRoot, "dist"));
20
+ });
21
+ });
22
+ describe("absolute output path", () => {
23
+ it("should preserve absolute path as-is", () => {
24
+ const result = (0, getOutputFolder_1.default)([bookRoot, absoluteOutput]);
25
+ expect(result).toBe(absoluteOutput);
26
+ });
27
+ });
28
+ describe("default output folder", () => {
29
+ it("should use _book in book root when no output specified", () => {
30
+ const result = (0, getOutputFolder_1.default)([bookRoot]);
31
+ expect(result).toBe(path_1.default.join(bookRoot, "_book"));
32
+ });
33
+ it("should use _book in cwd when no args", () => {
34
+ const result = (0, getOutputFolder_1.default)([]);
35
+ expect(result).toBe(path_1.default.join(process.cwd(), "_book"));
36
+ });
37
+ });
38
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=serve-integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve-integration.test.d.ts","sourceRoot":"","sources":["../../../src/cli/__tests__/serve-integration.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const path_1 = __importDefault(require("path"));
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const child_process_1 = require("child_process");
10
+ /**
11
+ * Integration tests for honkit serve rebuild behavior
12
+ *
13
+ * These tests verify that:
14
+ * 1. SUMMARY.md changes trigger full rebuild and generate new pages
15
+ * 2. New file additions are detected and processed
16
+ *
17
+ * This is a black-box test that starts serve, modifies files, and checks output.
18
+ */
19
+ jest.setTimeout(60000);
20
+ describe("serve integration", () => {
21
+ let tempDir;
22
+ let outputDir;
23
+ let serveProcess = null;
24
+ beforeEach(() => {
25
+ tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), "honkit-serve-test-"));
26
+ outputDir = path_1.default.join(tempDir, "_book");
27
+ });
28
+ afterEach(async () => {
29
+ // Kill serve process
30
+ if (serveProcess && !serveProcess.killed) {
31
+ serveProcess.kill("SIGKILL");
32
+ await new Promise((resolve) => {
33
+ serveProcess?.on("exit", resolve);
34
+ setTimeout(resolve, 1000);
35
+ });
36
+ }
37
+ serveProcess = null;
38
+ // Cleanup temp directory
39
+ if (tempDir && fs_1.default.existsSync(tempDir)) {
40
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
41
+ }
42
+ });
43
+ function createBookStructure() {
44
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, "README.md"), `# Test Book
45
+
46
+ Welcome to the test book.
47
+ `);
48
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, "SUMMARY.md"), `# Summary
49
+
50
+ * [Introduction](README.md)
51
+ `);
52
+ }
53
+ function getOutputFiles() {
54
+ if (!fs_1.default.existsSync(outputDir))
55
+ return [];
56
+ const files = [];
57
+ const walk = (dir, prefix = "") => {
58
+ const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
59
+ for (const entry of entries) {
60
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
61
+ if (entry.isDirectory()) {
62
+ walk(path_1.default.join(dir, entry.name), relativePath);
63
+ }
64
+ else {
65
+ files.push(relativePath);
66
+ }
67
+ }
68
+ };
69
+ walk(outputDir);
70
+ return files.sort();
71
+ }
72
+ function startServe() {
73
+ return new Promise((resolve, reject) => {
74
+ const binPath = path_1.default.resolve(__dirname, "../../../bin/honkit.js");
75
+ // Use port 0 to let OS assign an available port
76
+ serveProcess = (0, child_process_1.spawn)(process.execPath, [binPath, "serve", tempDir, outputDir, "--port", "0", "--lrport", "0"], {
77
+ stdio: ["pipe", "pipe", "pipe"],
78
+ env: { ...process.env }
79
+ });
80
+ let started = false;
81
+ const checkStarted = (output) => {
82
+ if (output.includes("Serving book on") && !started) {
83
+ started = true;
84
+ // Wait a bit for initial build to complete
85
+ setTimeout(resolve, 1000);
86
+ }
87
+ };
88
+ serveProcess.stdout?.on("data", (data) => {
89
+ checkStarted(data.toString());
90
+ });
91
+ serveProcess.stderr?.on("data", (data) => {
92
+ // HonKit outputs to stderr for info/warn
93
+ checkStarted(data.toString());
94
+ });
95
+ serveProcess.on("error", reject);
96
+ // Timeout after 10 seconds
97
+ setTimeout(() => {
98
+ if (!started) {
99
+ reject(new Error("Timeout waiting for serve to start"));
100
+ }
101
+ }, 10000);
102
+ });
103
+ }
104
+ function waitForFile(filePath, timeoutMs = 15000) {
105
+ return new Promise((resolve, reject) => {
106
+ const startTime = Date.now();
107
+ const check = () => {
108
+ if (fs_1.default.existsSync(filePath)) {
109
+ resolve();
110
+ }
111
+ else if (Date.now() - startTime > timeoutMs) {
112
+ reject(new Error(`Timeout waiting for file: ${filePath}`));
113
+ }
114
+ else {
115
+ setTimeout(check, 200);
116
+ }
117
+ };
118
+ check();
119
+ });
120
+ }
121
+ describe("file watching and rebuild", () => {
122
+ it("should generate new page when SUMMARY.md is updated during serve", async () => {
123
+ createBookStructure();
124
+ await startServe();
125
+ // Verify initial build - only index.html
126
+ expect(getOutputFiles()).toContain("index.html");
127
+ expect(getOutputFiles()).not.toContain("chapter1.html");
128
+ // Add chapter1.md
129
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, "chapter1.md"), `# Chapter 1
130
+
131
+ This is chapter 1.
132
+ `);
133
+ // Update SUMMARY.md to include chapter1
134
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, "SUMMARY.md"), `# Summary
135
+
136
+ * [Introduction](README.md)
137
+ * [Chapter 1](chapter1.md)
138
+ `);
139
+ // Wait for rebuild
140
+ await waitForFile(path_1.default.join(outputDir, "chapter1.html"));
141
+ // Verify chapter1.html was generated
142
+ expect(getOutputFiles()).toContain("chapter1.html");
143
+ });
144
+ it("should copy new asset file when added during serve", async () => {
145
+ createBookStructure();
146
+ await startServe();
147
+ // Verify initial state
148
+ expect(getOutputFiles()).not.toContain("notes.md");
149
+ // Add new asset file (not in SUMMARY.md)
150
+ fs_1.default.writeFileSync(path_1.default.join(tempDir, "notes.md"), `# Notes
151
+
152
+ These are my notes.
153
+ `);
154
+ // Wait for the asset to be copied
155
+ await waitForFile(path_1.default.join(outputDir, "notes.md"));
156
+ // Verify asset was copied
157
+ expect(getOutputFiles()).toContain("notes.md");
158
+ });
159
+ });
160
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=shouldFullRebuild.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shouldFullRebuild.test.d.ts","sourceRoot":"","sources":["../../../src/cli/__tests__/shouldFullRebuild.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const shouldFullRebuild_1 = require("../shouldFullRebuild");
4
+ describe("shouldFullRebuild", () => {
5
+ describe("structure files (change event)", () => {
6
+ it("should return true for SUMMARY.md change", () => {
7
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/SUMMARY.md", "change")).toBe(true);
8
+ });
9
+ it("should return true for SUMMARY.md (case insensitive)", () => {
10
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/summary.md", "change")).toBe(true);
11
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/Summary.md", "change")).toBe(true);
12
+ });
13
+ it("should return true for GLOSSARY.md change", () => {
14
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/GLOSSARY.md", "change")).toBe(true);
15
+ });
16
+ it("should return true for book.json change", () => {
17
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/book.json", "change")).toBe(true);
18
+ });
19
+ it("should return true for book.js change", () => {
20
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/book.js", "change")).toBe(true);
21
+ });
22
+ });
23
+ describe("new file additions (add event)", () => {
24
+ it("should return true for any new file", () => {
25
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/newfile.md", "add")).toBe(true);
26
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/image.png", "add")).toBe(true);
27
+ });
28
+ it("should return true for new structure files", () => {
29
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/SUMMARY.md", "add")).toBe(true);
30
+ });
31
+ });
32
+ describe("file deletions (unlink event)", () => {
33
+ it("should return true for any deleted file", () => {
34
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/deleted.md", "unlink")).toBe(true);
35
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/image.png", "unlink")).toBe(true);
36
+ });
37
+ it("should return true for deleted structure files", () => {
38
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/SUMMARY.md", "unlink")).toBe(true);
39
+ });
40
+ });
41
+ describe("content files (change event)", () => {
42
+ it("should return false for regular markdown file changes", () => {
43
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/README.md", "change")).toBe(false);
44
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/chapter1.md", "change")).toBe(false);
45
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/docs/page.md", "change")).toBe(false);
46
+ });
47
+ it("should return false for files with SUMMARY in path but different name", () => {
48
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/SUMMARY/page.md", "change")).toBe(false);
49
+ });
50
+ });
51
+ describe("without eventType (backwards compatibility)", () => {
52
+ it("should return true for structure files", () => {
53
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/SUMMARY.md")).toBe(true);
54
+ });
55
+ it("should return false for regular files", () => {
56
+ expect((0, shouldFullRebuild_1.shouldFullRebuild)("/path/to/book/README.md")).toBe(false);
57
+ });
58
+ });
59
+ });
60
+ describe("isStructureFile", () => {
61
+ it("should identify SUMMARY.md as structure file", () => {
62
+ expect((0, shouldFullRebuild_1.isStructureFile)("/path/to/SUMMARY.md")).toBe(true);
63
+ expect((0, shouldFullRebuild_1.isStructureFile)("/path/to/summary.md")).toBe(true);
64
+ });
65
+ it("should identify GLOSSARY.md as structure file", () => {
66
+ expect((0, shouldFullRebuild_1.isStructureFile)("/path/to/GLOSSARY.md")).toBe(true);
67
+ });
68
+ it("should identify book.json as structure file", () => {
69
+ expect((0, shouldFullRebuild_1.isStructureFile)("/path/to/book.json")).toBe(true);
70
+ });
71
+ it("should identify book.js as structure file", () => {
72
+ expect((0, shouldFullRebuild_1.isStructureFile)("/path/to/book.js")).toBe(true);
73
+ });
74
+ it("should NOT identify regular files as structure files", () => {
75
+ expect((0, shouldFullRebuild_1.isStructureFile)("/path/to/README.md")).toBe(false);
76
+ expect((0, shouldFullRebuild_1.isStructureFile)("/path/to/chapter.md")).toBe(false);
77
+ });
78
+ });
@@ -4,6 +4,6 @@
4
4
  @param {Array} args
5
5
  @return {string}
6
6
  */
7
- declare function getOutputFolder(args: any): string;
7
+ declare function getOutputFolder(args: any): any;
8
8
  export default getOutputFolder;
9
9
  //# sourceMappingURL=getOutputFolder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"getOutputFolder.d.ts","sourceRoot":"","sources":["../../src/cli/getOutputFolder.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,iBAAS,eAAe,CAAC,IAAI,KAAA,UAM5B;AAED,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"getOutputFolder.d.ts","sourceRoot":"","sources":["../../src/cli/getOutputFolder.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,iBAAS,eAAe,CAAC,IAAI,KAAA,OAa5B;AAED,eAAe,eAAe,CAAC"}
@@ -13,7 +13,14 @@ const path_1 = __importDefault(require("path"));
13
13
  function getOutputFolder(args) {
14
14
  const bookRoot = path_1.default.resolve(args[0] || process.cwd());
15
15
  const defaultOutputRoot = path_1.default.join(bookRoot, "_book");
16
- const outputFolder = args[1] ? path_1.default.resolve(process.cwd(), args[1]) : defaultOutputRoot;
16
+ // Fix: Resolve relative output folder paths against the book root (not process.cwd()).
17
+ // Absolute paths are preserved as-is. This ensures the output folder is correctly ignored
18
+ // by the file watcher. See: https://github.com/honkit/honkit/issues/491
19
+ const outputFolder = args[1]
20
+ ? path_1.default.isAbsolute(args[1])
21
+ ? args[1]
22
+ : path_1.default.resolve(bookRoot, args[1])
23
+ : defaultOutputRoot;
17
24
  return outputFolder;
18
25
  }
19
26
  exports.default = getOutputFolder;
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/cli/serve.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAkJA,wBA6DE"}
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/cli/serve.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA8JA,wBA6DE"}
package/lib/cli/serve.js CHANGED
@@ -15,9 +15,19 @@ const getBook_1 = __importDefault(require("./getBook"));
15
15
  const getOutputFolder_1 = __importDefault(require("./getOutputFolder"));
16
16
  const server_1 = __importDefault(require("./server"));
17
17
  const watch_1 = __importDefault(require("./watch"));
18
+ const shouldFullRebuild_1 = require("./shouldFullRebuild");
18
19
  const page_cache_1 = require("../output/page-cache");
19
20
  const fs_1 = __importDefault(require("fs"));
20
21
  let server, lrServer, lrPath;
22
+ function triggerLiveReload() {
23
+ if (lrPath) {
24
+ lrServer.changed({
25
+ body: {
26
+ files: [lrPath]
27
+ }
28
+ });
29
+ }
30
+ }
21
31
  function waitForCtrlC() {
22
32
  const d = promise_1.default.defer();
23
33
  process.on("SIGINT", () => {
@@ -67,7 +77,7 @@ function startServer(args, kwargs) {
67
77
  (0, watch_1.default)({
68
78
  watchDir: book.getRoot(),
69
79
  outputFolder,
70
- callback: (error, filepath) => {
80
+ callback: (error, filepath, eventType) => {
71
81
  if (error) {
72
82
  console.error(error);
73
83
  return;
@@ -80,9 +90,12 @@ function startServer(args, kwargs) {
80
90
  }
81
91
  // set livereload path
82
92
  lrPath = filepath;
83
- // TODO: use parse extension
84
- // Incremental update for pages
85
- if (lastOutput && filepath.endsWith(".md")) {
93
+ // Full rebuild is required for:
94
+ // 1. Structure files (SUMMARY.md, GLOSSARY.md, book.json, book.js)
95
+ // 2. New file additions (to update asset list and page structure)
96
+ const needsFullRebuild = (0, shouldFullRebuild_1.shouldFullRebuild)(filepath, eventType);
97
+ // Incremental update for existing pages (only for .md files that don't require full rebuild)
98
+ if (lastOutput && filepath.endsWith(".md") && !needsFullRebuild) {
86
99
  logger.warn.ok("Rebuild " + filepath);
87
100
  const changedOutput = lastOutput.reloadPage(lastOutput.book.getContentRoot(), filepath).merge({
88
101
  incrementalChangeFileSet: immutable_1.default.Set([filepath])
@@ -91,18 +104,13 @@ function startServer(args, kwargs) {
91
104
  output: changedOutput,
92
105
  Generator
93
106
  }).then(() => {
94
- if (lrPath && hasLiveReloading) {
95
- // trigger livereload
96
- lrServer.changed({
97
- body: {
98
- files: [lrPath]
99
- }
100
- });
101
- }
107
+ if (hasLiveReloading)
108
+ triggerLiveReload();
102
109
  });
103
110
  }
104
- // Asciidoc files are not supported for incremental build
105
- logger.info.ok("Rebuild " + filepath);
111
+ // Full rebuild for structure changes, new files, or non-markdown files
112
+ const reason = needsFullRebuild ? " (full rebuild)" : "";
113
+ logger.info.ok("Rebuild " + filepath + reason);
106
114
  return generateBook({
107
115
  book,
108
116
  outputFolder,
@@ -111,6 +119,8 @@ function startServer(args, kwargs) {
111
119
  reload
112
120
  }).then((output) => {
113
121
  lastOutput = output;
122
+ if (hasLiveReloading)
123
+ triggerLiveReload();
114
124
  });
115
125
  }
116
126
  });
@@ -0,0 +1,26 @@
1
+ import type { WatchEventType } from "./watch";
2
+ /**
3
+ * Determine if a file change requires a full rebuild based on the file path
4
+ *
5
+ * Full rebuild is needed when:
6
+ * - SUMMARY.md is changed (book structure changed)
7
+ * - GLOSSARY.md is changed (glossary definitions changed)
8
+ * - book.json or book.js is changed (configuration changed)
9
+ *
10
+ * @param filepath - The absolute path of the changed file
11
+ * @returns true if full rebuild is needed, false for incremental build
12
+ */
13
+ export declare function isStructureFile(filepath: string): boolean;
14
+ /**
15
+ * Determine if a file change requires a full rebuild
16
+ *
17
+ * Full rebuild is needed when:
18
+ * - Structure files are changed (SUMMARY.md, GLOSSARY.md, book.json, book.js)
19
+ * - Files are added or removed (to update asset list and page structure)
20
+ *
21
+ * @param filepath - The absolute path of the changed file
22
+ * @param eventType - The type of file change event
23
+ * @returns true if full rebuild is needed, false for incremental build
24
+ */
25
+ export declare function shouldFullRebuild(filepath: string, eventType?: WatchEventType): boolean;
26
+ //# sourceMappingURL=shouldFullRebuild.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shouldFullRebuild.d.ts","sourceRoot":"","sources":["../../src/cli/shouldFullRebuild.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAQzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,cAAc,GAAG,OAAO,CAQvF"}
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isStructureFile = isStructureFile;
7
+ exports.shouldFullRebuild = shouldFullRebuild;
8
+ const path_1 = __importDefault(require("path"));
9
+ /**
10
+ * Determine if a file change requires a full rebuild based on the file path
11
+ *
12
+ * Full rebuild is needed when:
13
+ * - SUMMARY.md is changed (book structure changed)
14
+ * - GLOSSARY.md is changed (glossary definitions changed)
15
+ * - book.json or book.js is changed (configuration changed)
16
+ *
17
+ * @param filepath - The absolute path of the changed file
18
+ * @returns true if full rebuild is needed, false for incremental build
19
+ */
20
+ function isStructureFile(filepath) {
21
+ const filename = path_1.default.basename(filepath);
22
+ const lowerFilename = filename.toLowerCase();
23
+ // Structure files that require full rebuild
24
+ const structureFiles = ["summary.md", "glossary.md", "book.json", "book.js"];
25
+ return structureFiles.includes(lowerFilename);
26
+ }
27
+ /**
28
+ * Determine if a file change requires a full rebuild
29
+ *
30
+ * Full rebuild is needed when:
31
+ * - Structure files are changed (SUMMARY.md, GLOSSARY.md, book.json, book.js)
32
+ * - Files are added or removed (to update asset list and page structure)
33
+ *
34
+ * @param filepath - The absolute path of the changed file
35
+ * @param eventType - The type of file change event
36
+ * @returns true if full rebuild is needed, false for incremental build
37
+ */
38
+ function shouldFullRebuild(filepath, eventType) {
39
+ // File additions or deletions always require full rebuild
40
+ if (eventType === "add" || eventType === "unlink") {
41
+ return true;
42
+ }
43
+ // Structure files require full rebuild
44
+ return isStructureFile(filepath);
45
+ }
@@ -1,4 +1,5 @@
1
1
  import { FSWatcher } from "chokidar";
2
+ export type WatchEventType = "add" | "addDir" | "change" | "unlink" | "unlinkDir";
2
3
  export interface WatchOptions {
3
4
  /**
4
5
  * Directory to watch
@@ -6,8 +7,11 @@ export interface WatchOptions {
6
7
  watchDir: string;
7
8
  /**
8
9
  * Callback when a file is modified
10
+ * @param error - Error if any
11
+ * @param filepath - Absolute path of the changed file
12
+ * @param eventType - Type of change: "add", "change", "unlink", etc.
9
13
  */
10
- callback: (error: Error | null, filepath?: string) => void;
14
+ callback: (error: Error | null, filepath?: string, eventType?: WatchEventType) => void;
11
15
  /**
12
16
  * Output folder to ignore (in addition to _book and node_modules)
13
17
  * This prevents infinite rebuild loops when using custom output folders
@@ -1 +1 @@
1
- {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/cli/watch.ts"],"names":[],"mappings":"AACA,OAAiB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG/C,MAAM,WAAW,YAAY;IACzB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,iBAAS,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,CA0C/C;AAED,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/cli/watch.ts"],"names":[],"mappings":"AACA,OAAiB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG/C,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAElF,MAAM,WAAW,YAAY;IACzB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;IACvF;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,iBAAS,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,CA0C/C;AAED,eAAe,KAAK,CAAC"}
package/lib/cli/watch.js CHANGED
@@ -40,8 +40,8 @@ function watch(options) {
40
40
  ignored: ignored,
41
41
  ignoreInitial: true
42
42
  });
43
- watcher.on("all", (e, filepath) => {
44
- callback(null, path_1.default.resolve(dir, filepath));
43
+ watcher.on("all", (eventType, filepath) => {
44
+ callback(null, path_1.default.resolve(dir, filepath), eventType);
45
45
  });
46
46
  watcher.on("error", (err) => {
47
47
  callback(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "honkit",
3
- "version": "6.1.5",
3
+ "version": "6.1.7",
4
4
  "description": "HonKit is building beautiful books using Markdown.",
5
5
  "keywords": [
6
6
  "git",
@@ -31,7 +31,7 @@
31
31
  ],
32
32
  "dependencies": {
33
33
  "bash-color": "^0.0.4",
34
- "cheerio": "^1.0.0",
34
+ "cheerio": "~1.0.0",
35
35
  "chokidar": "^3.6.0",
36
36
  "commander": "^5.1.0",
37
37
  "cp": "^0.2.0",
@@ -44,7 +44,7 @@
44
44
  "escape-html": "^1.0.3",
45
45
  "escape-string-regexp": "^4.0.0",
46
46
  "extend": "^3.0.2",
47
- "flat-cache": "^2.0.1",
47
+ "flat-cache": "^4.0.1",
48
48
  "front-matter": "^2.3.0",
49
49
  "gitbook-plugin-livereload": "^0.0.1",
50
50
  "gitbook-plugin-lunr": "^1.2.0",
@@ -52,9 +52,9 @@
52
52
  "github-slugid": "^1.0.1",
53
53
  "i18n-t": "^1.0.1",
54
54
  "ignore": "^5.3.2",
55
- "immutable": "^3.8.2",
55
+ "immutable": "^3.8.3",
56
56
  "is": "^3.3.0",
57
- "js-yaml": "^3.14.1",
57
+ "js-yaml": "^3.14.2",
58
58
  "json-schema-defaults": "^0.1.1",
59
59
  "jsonschema": "1.1.0",
60
60
  "juice": "^8.1.0",
@@ -70,16 +70,16 @@
70
70
  "q": "^1.5.1",
71
71
  "resolve": "^1.22.8",
72
72
  "semver": "^7.6.3",
73
- "send": "^0.17.2",
73
+ "send": "^0.19.2",
74
74
  "tiny-lr": "^1.1.1",
75
75
  "tmp": "0.2.4",
76
76
  "urijs": "^1.19.11",
77
- "@honkit/asciidoc": "6.1.5",
78
- "@honkit/honkit-plugin-fontsettings": "6.1.5",
79
- "@honkit/honkit-plugin-highlight": "6.1.5",
80
- "@honkit/honkit-plugin-theme-default": "6.1.5",
81
- "@honkit/markdown-legacy": "6.1.5",
82
- "@honkit/html": "6.1.5"
77
+ "@honkit/honkit-plugin-fontsettings": "6.1.7",
78
+ "@honkit/asciidoc": "6.1.7",
79
+ "@honkit/honkit-plugin-highlight": "6.1.7",
80
+ "@honkit/honkit-plugin-theme-default": "6.1.7",
81
+ "@honkit/html": "6.1.7",
82
+ "@honkit/markdown-legacy": "6.1.7"
83
83
  },
84
84
  "devDependencies": {
85
85
  "@relmify/jest-serializer-strip-ansi": "^1.0.2",
@@ -90,7 +90,7 @@
90
90
  "jest-mock-process": "^2.0.0",
91
91
  "rimraf": "^6.0.1",
92
92
  "typescript": "^5.6.2",
93
- "@honkit/internal-test-utils": "6.1.5"
93
+ "@honkit/internal-test-utils": "6.1.7"
94
94
  },
95
95
  "scripts": {
96
96
  "build": "tsc -p .",