honkit 6.1.4 → 6.1.6

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
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=watch.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.test.d.ts","sourceRoot":"","sources":["../../../src/cli/__tests__/watch.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,194 @@
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 watch_1 = __importDefault(require("../watch"));
10
+ /**
11
+ * Normalize path separators to forward slashes for cross-platform comparison
12
+ */
13
+ function normalizePath(filepath) {
14
+ return filepath.replace(/\\/g, "/");
15
+ }
16
+ describe("watch", () => {
17
+ // Increase timeout for file system operations (Windows may need more time)
18
+ jest.setTimeout(30000);
19
+ let tempDir;
20
+ let watchers = [];
21
+ beforeEach(() => {
22
+ tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), "honkit-watch-test-"));
23
+ });
24
+ afterEach(async () => {
25
+ // Close all watchers
26
+ await Promise.all(watchers.map((w) => w.close()));
27
+ watchers = [];
28
+ // Cleanup temp directory
29
+ if (tempDir && fs_1.default.existsSync(tempDir)) {
30
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
31
+ }
32
+ });
33
+ function createFile(relativePath, content = "# Test") {
34
+ const filePath = path_1.default.join(tempDir, relativePath);
35
+ const dir = path_1.default.dirname(filePath);
36
+ if (!fs_1.default.existsSync(dir)) {
37
+ fs_1.default.mkdirSync(dir, { recursive: true });
38
+ }
39
+ fs_1.default.writeFileSync(filePath, content);
40
+ return filePath;
41
+ }
42
+ function modifyFile(relativePath, content) {
43
+ const filePath = path_1.default.join(tempDir, relativePath);
44
+ fs_1.default.writeFileSync(filePath, content);
45
+ return filePath;
46
+ }
47
+ /**
48
+ * Wait for watcher to be ready and stable
49
+ */
50
+ async function waitForReady(watcher) {
51
+ await new Promise((resolve) => {
52
+ watcher.on("ready", resolve);
53
+ });
54
+ // Give the watcher a moment to stabilize after ready event
55
+ // Windows needs more time for file system operations
56
+ await new Promise((resolve) => setTimeout(resolve, 500));
57
+ }
58
+ /**
59
+ * Wait for a file change to be detected that matches the predicate
60
+ * Paths are normalized to forward slashes for cross-platform compatibility
61
+ */
62
+ function waitForChange(watcher, predicate, timeoutMs = 20000) {
63
+ return new Promise((resolve, reject) => {
64
+ const timeout = setTimeout(() => {
65
+ reject(new Error(`Timeout waiting for file change after ${timeoutMs}ms`));
66
+ }, timeoutMs);
67
+ const handler = (_event, filepath) => {
68
+ const fullPath = normalizePath(path_1.default.resolve(tempDir, filepath));
69
+ if (predicate(fullPath)) {
70
+ clearTimeout(timeout);
71
+ watcher.off("all", handler);
72
+ resolve(fullPath);
73
+ }
74
+ };
75
+ watcher.on("all", handler);
76
+ });
77
+ }
78
+ describe("issue #491 - custom output folder handling", () => {
79
+ it("should detect changes in custom output folder when not specified in options", async () => {
80
+ createFile("README.md", "# README");
81
+ createFile("output/orphan.md", "# Orphan in output folder");
82
+ const detectedFiles = [];
83
+ // Start watching WITHOUT specifying output folder
84
+ const watcher = (0, watch_1.default)({
85
+ watchDir: tempDir,
86
+ callback: (error, filepath) => {
87
+ if (error || !filepath)
88
+ return;
89
+ detectedFiles.push(normalizePath(filepath));
90
+ }
91
+ });
92
+ watchers.push(watcher);
93
+ await waitForReady(watcher);
94
+ // Modify file in output folder and wait for detection
95
+ modifyFile("output/orphan.md", "# Modified");
96
+ const detected = await waitForChange(watcher, (f) => f.includes("output/orphan.md"));
97
+ expect(detected).toContain("output/orphan.md");
98
+ });
99
+ it("should NOT detect changes in custom output folder when specified in options", async () => {
100
+ createFile("README.md", "# README");
101
+ createFile("output/orphan.md", "# Orphan in output folder");
102
+ const detectedFiles = [];
103
+ // Start watching WITH output folder specified - it should be ignored
104
+ const watcher = (0, watch_1.default)({
105
+ watchDir: tempDir,
106
+ outputFolder: "output",
107
+ callback: (error, filepath) => {
108
+ if (error || !filepath)
109
+ return;
110
+ detectedFiles.push(normalizePath(filepath));
111
+ }
112
+ });
113
+ watchers.push(watcher);
114
+ await waitForReady(watcher);
115
+ // Modify both files - output should be ignored, README should be detected
116
+ modifyFile("output/orphan.md", "# Modified");
117
+ modifyFile("README.md", "# Modified README");
118
+ // Wait for README change to be detected (proves watcher is working)
119
+ await waitForChange(watcher, (f) => f.includes("README.md"));
120
+ // Verify output folder change was NOT detected
121
+ const outputChanges = detectedFiles.filter((f) => f.includes("output/"));
122
+ expect(outputChanges).toHaveLength(0);
123
+ });
124
+ it("should handle absolute output folder paths", async () => {
125
+ createFile("README.md", "# README");
126
+ createFile("docs-output/file.md", "# File");
127
+ const detectedFiles = [];
128
+ const absoluteOutputPath = path_1.default.join(tempDir, "docs-output");
129
+ const watcher = (0, watch_1.default)({
130
+ watchDir: tempDir,
131
+ outputFolder: absoluteOutputPath,
132
+ callback: (error, filepath) => {
133
+ if (error || !filepath)
134
+ return;
135
+ detectedFiles.push(normalizePath(filepath));
136
+ }
137
+ });
138
+ watchers.push(watcher);
139
+ await waitForReady(watcher);
140
+ // Modify both files
141
+ modifyFile("docs-output/file.md", "# Modified");
142
+ modifyFile("README.md", "# Modified README");
143
+ // Wait for README change
144
+ await waitForChange(watcher, (f) => f.includes("README.md"));
145
+ // Verify docs-output was NOT detected
146
+ const outputChanges = detectedFiles.filter((f) => f.includes("docs-output/"));
147
+ expect(outputChanges).toHaveLength(0);
148
+ });
149
+ });
150
+ describe("default _book folder", () => {
151
+ it("should NOT detect changes in _book folder", async () => {
152
+ createFile("file1.md", "# File 1");
153
+ createFile("file2.md", "# File 2");
154
+ createFile("_book/output.md", "# Output");
155
+ const detectedFiles = [];
156
+ const watcher = (0, watch_1.default)({
157
+ watchDir: tempDir,
158
+ callback: (error, filepath) => {
159
+ if (error || !filepath)
160
+ return;
161
+ detectedFiles.push(normalizePath(filepath));
162
+ }
163
+ });
164
+ watchers.push(watcher);
165
+ await waitForReady(watcher);
166
+ // First modify file1 to ensure watcher is working
167
+ modifyFile("file1.md", "# Modified File 1");
168
+ await waitForChange(watcher, (f) => f.includes("file1.md"));
169
+ // Now modify _book file (should be ignored)
170
+ modifyFile("_book/output.md", "# Modified");
171
+ // Modify file2 to confirm watcher still works
172
+ modifyFile("file2.md", "# Modified File 2");
173
+ await waitForChange(watcher, (f) => f.includes("file2.md"));
174
+ // Verify _book was NOT detected
175
+ const bookChanges = detectedFiles.filter((f) => f.includes("_book"));
176
+ expect(bookChanges).toHaveLength(0);
177
+ });
178
+ });
179
+ describe("source files", () => {
180
+ it("should detect changes to markdown files in source directory", async () => {
181
+ createFile("README.md", "# README");
182
+ createFile("orphan.md", "# Orphan - not in SUMMARY");
183
+ const watcher = (0, watch_1.default)({
184
+ watchDir: tempDir,
185
+ callback: () => { }
186
+ });
187
+ watchers.push(watcher);
188
+ await waitForReady(watcher);
189
+ modifyFile("orphan.md", "# Modified orphan");
190
+ const detected = await waitForChange(watcher, (f) => f.includes("orphan.md"));
191
+ expect(detected).toContain("orphan.md");
192
+ });
193
+ });
194
+ });
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;AA4IA,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", () => {
@@ -62,51 +72,57 @@ function startServer(args, kwargs) {
62
72
  return waitForCtrlC();
63
73
  }
64
74
  // update book immutably. does not use book again
65
- return (0, watch_1.default)(book.getRoot(), (error, filepath) => {
66
- if (error) {
67
- console.error(error);
68
- return;
69
- }
70
- // If the file does not exist in file system, show a warning and skip
71
- // Probably, the file has been deleted
72
- if (!fs_1.default.existsSync(filepath)) {
73
- logger.warn.ok(`${filepath} does not exist in file system.`);
74
- return;
75
- }
76
- // set livereload path
77
- lrPath = filepath;
78
- // TODO: use parse extension
79
- // Incremental update for pages
80
- if (lastOutput && filepath.endsWith(".md")) {
81
- logger.warn.ok("Rebuild " + filepath);
82
- const changedOutput = lastOutput.reloadPage(lastOutput.book.getContentRoot(), filepath).merge({
83
- incrementalChangeFileSet: immutable_1.default.Set([filepath])
84
- });
85
- return incrementalBuild({
86
- output: changedOutput,
87
- Generator
88
- }).then(() => {
89
- if (lrPath && hasLiveReloading) {
90
- // trigger livereload
91
- lrServer.changed({
92
- body: {
93
- files: [lrPath]
94
- }
95
- });
96
- }
75
+ // Pass outputFolder to watch to prevent infinite rebuild loops
76
+ // https://github.com/honkit/honkit/issues/491
77
+ (0, watch_1.default)({
78
+ watchDir: book.getRoot(),
79
+ outputFolder,
80
+ callback: (error, filepath, eventType) => {
81
+ if (error) {
82
+ console.error(error);
83
+ return;
84
+ }
85
+ // If the file does not exist in file system, show a warning and skip
86
+ // Probably, the file has been deleted
87
+ if (!fs_1.default.existsSync(filepath)) {
88
+ logger.warn.ok(`${filepath} does not exist in file system.`);
89
+ return;
90
+ }
91
+ // set livereload path
92
+ lrPath = filepath;
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) {
99
+ logger.warn.ok("Rebuild " + filepath);
100
+ const changedOutput = lastOutput.reloadPage(lastOutput.book.getContentRoot(), filepath).merge({
101
+ incrementalChangeFileSet: immutable_1.default.Set([filepath])
102
+ });
103
+ return incrementalBuild({
104
+ output: changedOutput,
105
+ Generator
106
+ }).then(() => {
107
+ if (hasLiveReloading)
108
+ triggerLiveReload();
109
+ });
110
+ }
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);
114
+ return generateBook({
115
+ book,
116
+ outputFolder,
117
+ hasLiveReloading,
118
+ Generator,
119
+ reload
120
+ }).then((output) => {
121
+ lastOutput = output;
122
+ if (hasLiveReloading)
123
+ triggerLiveReload();
97
124
  });
98
125
  }
99
- // Asciidoc files are not supported for incremental build
100
- logger.info.ok("Rebuild " + filepath);
101
- return generateBook({
102
- book,
103
- outputFolder,
104
- hasLiveReloading,
105
- Generator,
106
- reload
107
- }).then((output) => {
108
- lastOutput = output;
109
- });
110
126
  });
111
127
  });
112
128
  }
@@ -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,10 +1,30 @@
1
+ import { FSWatcher } from "chokidar";
2
+ export type WatchEventType = "add" | "addDir" | "change" | "unlink" | "unlinkDir";
3
+ export interface WatchOptions {
4
+ /**
5
+ * Directory to watch
6
+ */
7
+ watchDir: string;
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.
13
+ */
14
+ callback: (error: Error | null, filepath?: string, eventType?: WatchEventType) => void;
15
+ /**
16
+ * Output folder to ignore (in addition to _book and node_modules)
17
+ * This prevents infinite rebuild loops when using custom output folders
18
+ * @see https://github.com/honkit/honkit/issues/491
19
+ */
20
+ outputFolder?: string;
21
+ }
1
22
  /**
2
- Watch a folder and resolve promise once a file is modified
3
-
4
- @param {string} dir
5
- @param callback
6
- @return {Promise}
23
+ * Watch a folder and call callback when a file is modified
24
+ *
25
+ * @param {WatchOptions} options
26
+ * @return {FSWatcher} The chokidar watcher instance
7
27
  */
8
- declare function watch(dir: any, callback: any): void;
28
+ declare function watch(options: WatchOptions): FSWatcher;
9
29
  export default watch;
10
30
  //# sourceMappingURL=watch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/cli/watch.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AAEH,iBAAS,KAAK,CAAC,GAAG,KAAA,EAAE,QAAQ,KAAA,QAwB3B;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
@@ -7,31 +7,45 @@ const path_1 = __importDefault(require("path"));
7
7
  const chokidar_1 = __importDefault(require("chokidar"));
8
8
  const parsers_1 = __importDefault(require("../parsers"));
9
9
  /**
10
- Watch a folder and resolve promise once a file is modified
11
-
12
- @param {string} dir
13
- @param callback
14
- @return {Promise}
10
+ * Watch a folder and call callback when a file is modified
11
+ *
12
+ * @param {WatchOptions} options
13
+ * @return {FSWatcher} The chokidar watcher instance
15
14
  */
16
- function watch(dir, callback) {
17
- dir = path_1.default.resolve(dir);
15
+ function watch(options) {
16
+ const { callback, outputFolder } = options;
17
+ const dir = path_1.default.resolve(options.watchDir);
18
18
  const toWatch = ["book.json", "book.js", "_layouts/**"];
19
19
  // Watch all parsable files
20
20
  parsers_1.default.extensions.forEach((ext) => {
21
21
  toWatch.push(`**/*${ext}`);
22
22
  });
23
+ // Build ignored patterns
24
+ // Always ignore _book and node_modules
25
+ // https://github.com/honkit/honkit/issues/269
26
+ const ignored = ["_book/**", "node_modules/**"];
27
+ // If a custom output folder is specified, ignore it too
28
+ // This prevents infinite rebuild loops when output folder is inside the watched directory
29
+ // https://github.com/honkit/honkit/issues/491
30
+ if (outputFolder) {
31
+ // Convert to forward slashes for glob pattern (Windows compatibility)
32
+ const outputRelative = path_1.default.relative(dir, path_1.default.resolve(dir, outputFolder)).replace(/\\/g, "/");
33
+ // Only add to ignored if the output folder is inside the watched directory
34
+ if (outputRelative && !outputRelative.startsWith("..") && !path_1.default.isAbsolute(outputRelative)) {
35
+ ignored.push(`${outputRelative}/**`);
36
+ }
37
+ }
23
38
  const watcher = chokidar_1.default.watch(toWatch, {
24
39
  cwd: dir,
25
- // prevent infinity loop
26
- // https://github.com/honkit/honkit/issues/269
27
- ignored: ["_book/**", "node_modules/**"],
40
+ ignored: ignored,
28
41
  ignoreInitial: true
29
42
  });
30
- watcher.on("all", (e, filepath) => {
31
- callback(null, path_1.default.resolve(dir, filepath));
43
+ watcher.on("all", (eventType, filepath) => {
44
+ callback(null, path_1.default.resolve(dir, filepath), eventType);
32
45
  });
33
46
  watcher.on("error", (err) => {
34
47
  callback(err);
35
48
  });
49
+ return watcher;
36
50
  }
37
51
  exports.default = watch;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "honkit",
3
- "version": "6.1.4",
3
+ "version": "6.1.6",
4
4
  "description": "HonKit is building beautiful books using Markdown.",
5
5
  "keywords": [
6
6
  "git",
@@ -74,12 +74,12 @@
74
74
  "tiny-lr": "^1.1.1",
75
75
  "tmp": "0.2.4",
76
76
  "urijs": "^1.19.11",
77
- "@honkit/asciidoc": "6.1.4",
78
- "@honkit/honkit-plugin-fontsettings": "6.1.4",
79
- "@honkit/honkit-plugin-theme-default": "6.1.4",
80
- "@honkit/honkit-plugin-highlight": "6.1.4",
81
- "@honkit/html": "6.1.4",
82
- "@honkit/markdown-legacy": "6.1.4"
77
+ "@honkit/honkit-plugin-highlight": "6.1.6",
78
+ "@honkit/honkit-plugin-fontsettings": "6.1.6",
79
+ "@honkit/html": "6.1.6",
80
+ "@honkit/markdown-legacy": "6.1.6",
81
+ "@honkit/asciidoc": "6.1.6",
82
+ "@honkit/honkit-plugin-theme-default": "6.1.6"
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.4"
93
+ "@honkit/internal-test-utils": "6.1.6"
94
94
  },
95
95
  "scripts": {
96
96
  "build": "tsc -p .",