docula 0.50.0 → 1.0.0

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.
package/dist/docula.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/docula.ts
2
- import fs3 from "fs";
2
+ import fs4 from "fs";
3
3
  import http from "http";
4
- import path5 from "path";
4
+ import path6 from "path";
5
5
  import process4 from "process";
6
6
  import { pathToFileURL } from "url";
7
7
  import { createJiti } from "jiti";
@@ -9,62 +9,581 @@ import handler from "serve-handler";
9
9
  import updateNotifier from "update-notifier";
10
10
 
11
11
  // src/builder.ts
12
- import fs2 from "fs";
13
- import path4 from "path";
12
+ import fs3 from "fs";
13
+ import path5 from "path";
14
14
  import { Ecto } from "ecto";
15
15
  import { Writr } from "writr";
16
16
 
17
+ // src/api-parser.ts
18
+ function parseOpenApiSpec(specJson) {
19
+ const spec = JSON.parse(specJson);
20
+ const info = {
21
+ title: spec.info?.title ?? "",
22
+ description: spec.info?.description ?? "",
23
+ version: spec.info?.version ?? ""
24
+ };
25
+ const servers = [];
26
+ if (Array.isArray(spec.servers)) {
27
+ for (const server of spec.servers) {
28
+ servers.push({
29
+ url: server.url ?? "",
30
+ description: server.description ?? ""
31
+ });
32
+ }
33
+ }
34
+ const tagDescriptions = /* @__PURE__ */ new Map();
35
+ if (Array.isArray(spec.tags)) {
36
+ for (const tag of spec.tags) {
37
+ if (tag.name) {
38
+ tagDescriptions.set(tag.name, tag.description ?? "");
39
+ }
40
+ }
41
+ }
42
+ const groupedOperations = /* @__PURE__ */ new Map();
43
+ const paths = spec.paths ?? {};
44
+ for (const [pathStr, pathItem] of Object.entries(paths)) {
45
+ if (!pathItem || typeof pathItem !== "object") {
46
+ continue;
47
+ }
48
+ const methods = [
49
+ "get",
50
+ "post",
51
+ "put",
52
+ "delete",
53
+ "patch",
54
+ "head",
55
+ "options"
56
+ ];
57
+ for (const method of methods) {
58
+ const operation = pathItem[method];
59
+ if (!operation) {
60
+ continue;
61
+ }
62
+ const tags = Array.isArray(operation.tags) && operation.tags.length > 0 ? operation.tags : ["Default"];
63
+ const parameters = extractParameters(operation, pathItem, spec);
64
+ const requestBody = extractRequestBody(operation, spec);
65
+ const responses = extractResponses(operation, spec);
66
+ const baseUrl = servers.length > 0 ? servers[0].url : "";
67
+ const codeExamples = generateCodeExamples(
68
+ method,
69
+ pathStr,
70
+ baseUrl,
71
+ parameters,
72
+ requestBody
73
+ );
74
+ const operationId = operation.operationId ?? `${method}-${pathStr.replaceAll(/[^a-zA-Z0-9]/g, "-")}`;
75
+ const apiOperation = {
76
+ id: slugify(operationId),
77
+ method,
78
+ methodUpper: method.toUpperCase(),
79
+ path: pathStr,
80
+ summary: operation.summary ?? "",
81
+ description: operation.description ?? "",
82
+ parameters,
83
+ requestBody,
84
+ responses,
85
+ codeExamples
86
+ };
87
+ for (const tag of tags) {
88
+ const existing = groupedOperations.get(tag) ?? [];
89
+ existing.push(apiOperation);
90
+ groupedOperations.set(tag, existing);
91
+ }
92
+ }
93
+ }
94
+ const groups = [];
95
+ for (const [name, operations] of groupedOperations) {
96
+ groups.push({
97
+ name,
98
+ description: tagDescriptions.get(name) ?? "",
99
+ id: slugify(name),
100
+ operations
101
+ });
102
+ }
103
+ return { info, servers, groups };
104
+ }
105
+ function getStatusClass(statusCode) {
106
+ if (statusCode.startsWith("2")) {
107
+ return "2xx";
108
+ }
109
+ if (statusCode.startsWith("3")) {
110
+ return "3xx";
111
+ }
112
+ if (statusCode.startsWith("4")) {
113
+ return "4xx";
114
+ }
115
+ if (statusCode.startsWith("5")) {
116
+ return "5xx";
117
+ }
118
+ return "default";
119
+ }
120
+ function slugify(text) {
121
+ return text.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
122
+ }
123
+ function resolveRef(ref, spec) {
124
+ if (!ref.startsWith("#/")) {
125
+ return void 0;
126
+ }
127
+ const parts = ref.slice(2).split("/");
128
+ let current = spec;
129
+ for (const part of parts) {
130
+ if (current && typeof current === "object" && part in current) {
131
+ current = current[part];
132
+ } else {
133
+ return void 0;
134
+ }
135
+ }
136
+ return current;
137
+ }
138
+ function resolveSchema(schema, spec, visited = /* @__PURE__ */ new Set()) {
139
+ if (!schema) {
140
+ return void 0;
141
+ }
142
+ if (schema.$ref) {
143
+ if (visited.has(schema.$ref)) {
144
+ return { type: "object", description: "(circular reference)" };
145
+ }
146
+ visited.add(schema.$ref);
147
+ const resolved = resolveRef(schema.$ref, spec);
148
+ return resolved ? resolveSchema(resolved, spec, visited) : void 0;
149
+ }
150
+ return schema;
151
+ }
152
+ function extractSchemaProperties(schema, spec, visited = /* @__PURE__ */ new Set()) {
153
+ const resolved = resolveSchema(schema, spec, new Set(visited));
154
+ if (!resolved) {
155
+ return [];
156
+ }
157
+ const properties = [];
158
+ const requiredFields = Array.isArray(resolved.required) ? resolved.required : [];
159
+ if (resolved.properties && typeof resolved.properties === "object") {
160
+ for (const [name, propSchema] of Object.entries(resolved.properties)) {
161
+ const prop = resolveSchema(
162
+ propSchema,
163
+ spec,
164
+ new Set(visited)
165
+ );
166
+ properties.push({
167
+ name,
168
+ type: getSchemaType(prop),
169
+ required: requiredFields.includes(name),
170
+ description: prop?.description ?? "",
171
+ enumValues: Array.isArray(prop?.enum) ? prop.enum : void 0
172
+ });
173
+ }
174
+ } else if (resolved.type === "array" && resolved.items) {
175
+ const itemSchema = resolveSchema(
176
+ resolved.items,
177
+ spec,
178
+ new Set(visited)
179
+ );
180
+ properties.push({
181
+ name: "(items)",
182
+ type: getSchemaType(itemSchema),
183
+ required: false,
184
+ description: itemSchema?.description ?? ""
185
+ });
186
+ } else if (resolved.type && resolved.type !== "object") {
187
+ properties.push({
188
+ name: "(value)",
189
+ type: resolved.type,
190
+ required: false,
191
+ description: resolved.description ?? ""
192
+ });
193
+ }
194
+ return properties;
195
+ }
196
+ function getSchemaType(schema) {
197
+ if (!schema) {
198
+ return "unknown";
199
+ }
200
+ if (schema.type === "array") {
201
+ const items = schema.items;
202
+ const itemType = items?.type ?? "any";
203
+ return `array<${itemType}>`;
204
+ }
205
+ if (schema.type) {
206
+ if (schema.format) {
207
+ return `${schema.type} (${schema.format})`;
208
+ }
209
+ return schema.type;
210
+ }
211
+ if (schema.oneOf || schema.anyOf) {
212
+ return "oneOf";
213
+ }
214
+ if (schema.allOf) {
215
+ return "allOf";
216
+ }
217
+ return "object";
218
+ }
219
+ function extractParameters(operation, pathItem, spec) {
220
+ const parameters = [];
221
+ const allParams = [
222
+ ...Array.isArray(pathItem.parameters) ? pathItem.parameters : [],
223
+ ...Array.isArray(operation.parameters) ? operation.parameters : []
224
+ ];
225
+ for (const param of allParams) {
226
+ const resolved = param.$ref ? resolveRef(param.$ref, spec) : param;
227
+ if (!resolved) {
228
+ continue;
229
+ }
230
+ const paramSchema = resolveSchema(
231
+ resolved.schema,
232
+ spec
233
+ );
234
+ parameters.push({
235
+ name: resolved.name ?? "",
236
+ in: resolved.in ?? "",
237
+ required: Boolean(resolved.required),
238
+ type: getSchemaType(paramSchema),
239
+ description: resolved.description ?? ""
240
+ });
241
+ }
242
+ return parameters;
243
+ }
244
+ function extractRequestBody(operation, spec) {
245
+ let requestBody = operation.requestBody;
246
+ if (!requestBody) {
247
+ return void 0;
248
+ }
249
+ if (requestBody.$ref) {
250
+ requestBody = resolveRef(requestBody.$ref, spec);
251
+ if (!requestBody) {
252
+ return void 0;
253
+ }
254
+ }
255
+ const content = requestBody.content;
256
+ if (!content) {
257
+ return void 0;
258
+ }
259
+ const contentType = Object.keys(content)[0] ?? "application/json";
260
+ const mediaType = content[contentType];
261
+ if (!mediaType) {
262
+ return void 0;
263
+ }
264
+ const schema = mediaType.schema;
265
+ const schemaProperties = extractSchemaProperties(schema, spec);
266
+ let example = "";
267
+ if (mediaType.example) {
268
+ example = JSON.stringify(mediaType.example, null, 2);
269
+ } else if (schema) {
270
+ const generated = generateExampleFromSchema(schema, spec);
271
+ if (generated !== void 0) {
272
+ example = JSON.stringify(generated, null, 2);
273
+ }
274
+ }
275
+ return { contentType, schemaProperties, example };
276
+ }
277
+ function extractResponses(operation, spec) {
278
+ const responses = [];
279
+ const responsesObj = operation.responses;
280
+ if (!responsesObj) {
281
+ return responses;
282
+ }
283
+ for (const [statusCode, responseObj] of Object.entries(responsesObj)) {
284
+ let response = responseObj;
285
+ if (response.$ref) {
286
+ const resolved = resolveRef(response.$ref, spec);
287
+ if (!resolved) {
288
+ continue;
289
+ }
290
+ response = resolved;
291
+ }
292
+ const content = response.content;
293
+ let contentType = "";
294
+ let schemaProperties = [];
295
+ let example = "";
296
+ if (content) {
297
+ contentType = Object.keys(content)[0] ?? "";
298
+ const mediaType = content[contentType];
299
+ if (mediaType?.schema) {
300
+ schemaProperties = extractSchemaProperties(
301
+ mediaType.schema,
302
+ spec
303
+ );
304
+ if (mediaType.example) {
305
+ example = JSON.stringify(mediaType.example, null, 2);
306
+ } else {
307
+ const generated = generateExampleFromSchema(
308
+ mediaType.schema,
309
+ spec
310
+ );
311
+ if (generated !== void 0) {
312
+ example = JSON.stringify(generated, null, 2);
313
+ }
314
+ }
315
+ }
316
+ }
317
+ responses.push({
318
+ statusCode,
319
+ statusClass: getStatusClass(statusCode),
320
+ /* v8 ignore next */
321
+ description: response.description ?? "",
322
+ contentType,
323
+ schemaProperties,
324
+ example
325
+ });
326
+ }
327
+ return responses;
328
+ }
329
+ function generateExampleFromSchema(schema, spec, visited = /* @__PURE__ */ new Set()) {
330
+ if (!schema) {
331
+ return void 0;
332
+ }
333
+ if (schema.$ref) {
334
+ if (visited.has(schema.$ref)) {
335
+ return {};
336
+ }
337
+ visited.add(schema.$ref);
338
+ const resolved = resolveRef(schema.$ref, spec);
339
+ return resolved ? generateExampleFromSchema(resolved, spec, visited) : void 0;
340
+ }
341
+ if (schema.example !== void 0) {
342
+ return schema.example;
343
+ }
344
+ if (schema.type === "object" || schema.properties) {
345
+ const result = {};
346
+ if (schema.properties) {
347
+ for (const [name, propSchema] of Object.entries(schema.properties)) {
348
+ result[name] = generateExampleFromSchema(
349
+ propSchema,
350
+ spec,
351
+ new Set(visited)
352
+ );
353
+ }
354
+ }
355
+ if (schema.additionalProperties === true) {
356
+ result.key = "value";
357
+ }
358
+ return result;
359
+ }
360
+ if (schema.type === "array") {
361
+ const itemExample = generateExampleFromSchema(
362
+ schema.items,
363
+ spec,
364
+ new Set(visited)
365
+ );
366
+ return itemExample !== void 0 ? [itemExample] : [];
367
+ }
368
+ if (schema.enum && Array.isArray(schema.enum) && schema.enum.length > 0) {
369
+ return schema.enum[0];
370
+ }
371
+ const defaults = {
372
+ string: "string",
373
+ number: 0,
374
+ integer: 0,
375
+ boolean: true
376
+ };
377
+ return defaults[schema.type] ?? null;
378
+ }
379
+ function generateCodeExamples(method, pathStr, baseUrl, parameters, requestBody) {
380
+ const url = `${baseUrl}${pathStr}`;
381
+ const queryParams = parameters.filter((p) => p.in === "query");
382
+ const headerParams = parameters.filter((p) => p.in === "header");
383
+ const queryString = queryParams.length > 0 ? `?${queryParams.map((p) => `${p.name}={${p.name}}`).join("&")}` : "";
384
+ const fullUrl = `${url}${queryString}`;
385
+ const curlParts = [`curl -X ${method.toUpperCase()} "${fullUrl}"`];
386
+ for (const header of headerParams) {
387
+ curlParts.push(` -H "${header.name}: {${header.name}}"`);
388
+ }
389
+ if (requestBody) {
390
+ curlParts.push(` -H "Content-Type: ${requestBody.contentType}"`);
391
+ if (requestBody.example) {
392
+ curlParts.push(` -d '${requestBody.example.replaceAll("\n", "")}'`);
393
+ }
394
+ }
395
+ const curl = curlParts.join(" \\\n");
396
+ const jsParts = [];
397
+ const fetchOptions = [];
398
+ fetchOptions.push(` method: '${method.toUpperCase()}'`);
399
+ const jsHeaders = [];
400
+ for (const header of headerParams) {
401
+ jsHeaders.push(` '${header.name}': '{${header.name}}'`);
402
+ }
403
+ if (requestBody) {
404
+ jsHeaders.push(` 'Content-Type': '${requestBody.contentType}'`);
405
+ }
406
+ if (jsHeaders.length > 0) {
407
+ fetchOptions.push(` headers: {
408
+ ${jsHeaders.join(",\n")}
409
+ }`);
410
+ }
411
+ if (requestBody?.example) {
412
+ fetchOptions.push(
413
+ ` body: JSON.stringify(${requestBody.example.replaceAll("\n", "")})`
414
+ );
415
+ }
416
+ jsParts.push(`const response = await fetch('${fullUrl}', {`);
417
+ jsParts.push(fetchOptions.join(",\n"));
418
+ jsParts.push("});");
419
+ jsParts.push("const data = await response.json();");
420
+ const javascript = jsParts.join("\n");
421
+ const pyParts = [];
422
+ pyParts.push("import requests");
423
+ pyParts.push("");
424
+ const pyArgs = [];
425
+ if (queryParams.length > 0) {
426
+ const paramsObj = queryParams.map((p) => ` '${p.name}': '{${p.name}}'`).join(",\n");
427
+ pyArgs.push(` params={
428
+ ${paramsObj}
429
+ }`);
430
+ }
431
+ const pyHeaders = [];
432
+ for (const header of headerParams) {
433
+ pyHeaders.push(` '${header.name}': '{${header.name}}'`);
434
+ }
435
+ if (requestBody) {
436
+ pyHeaders.push(` 'Content-Type': '${requestBody.contentType}'`);
437
+ }
438
+ if (pyHeaders.length > 0) {
439
+ pyArgs.push(` headers={
440
+ ${pyHeaders.join(",\n")}
441
+ }`);
442
+ }
443
+ if (requestBody?.example) {
444
+ pyArgs.push(` json=${requestBody.example.replaceAll("\n", "")}`);
445
+ }
446
+ const pyUrl = queryParams.length > 0 ? url : fullUrl;
447
+ if (pyArgs.length > 0) {
448
+ pyParts.push(`response = requests.${method}(`);
449
+ pyParts.push(` '${pyUrl}',`);
450
+ pyParts.push(`${pyArgs.join(",\n")}`);
451
+ pyParts.push(")");
452
+ } else {
453
+ pyParts.push(`response = requests.${method}('${pyUrl}')`);
454
+ }
455
+ pyParts.push("data = response.json()");
456
+ const python = pyParts.join("\n");
457
+ return { curl, javascript, python };
458
+ }
459
+
17
460
  // src/console.ts
18
461
  import path from "path";
19
462
  import process from "process";
463
+ import {
464
+ blue,
465
+ bold,
466
+ cyan,
467
+ dim,
468
+ gray,
469
+ green,
470
+ magenta,
471
+ red,
472
+ yellow
473
+ } from "colorette";
474
+ var ansiRegex = /\u001B\[[0-9;]*m/g;
20
475
  var DoculaConsole = class {
21
476
  log(message) {
22
477
  console.log(message);
23
478
  }
24
479
  error(message) {
25
- console.error(message);
480
+ console.error(red(bold(`\u2718 [error] ${message}`)));
26
481
  }
27
482
  warn(message) {
28
- console.warn(message);
483
+ console.warn(yellow(`\u26A0 [warn] ${message}`));
484
+ }
485
+ success(message) {
486
+ console.log(green(bold(`\u2714 ${message}`)));
487
+ }
488
+ info(message) {
489
+ console.log(cyan(`\u2139 ${message}`));
490
+ }
491
+ step(message) {
492
+ console.log(bold(blue(`\u25B6 ${message}`)));
493
+ }
494
+ fileBuilt(filePath) {
495
+ console.log(dim(` \u2192 ${filePath}`));
496
+ }
497
+ fileCopied(filePath) {
498
+ this.fileBuilt(filePath);
499
+ }
500
+ serverLog(method, url, statusCode, durationMs) {
501
+ let statusColor = green;
502
+ if (statusCode >= 400) {
503
+ statusColor = red;
504
+ } else if (statusCode >= 300) {
505
+ statusColor = yellow;
506
+ }
507
+ const sanitizedMethod = method.replace(ansiRegex, "");
508
+ const sanitizedUrl = url.replace(ansiRegex, "");
509
+ const parts = [
510
+ ` ${bold(sanitizedMethod)}`,
511
+ sanitizedUrl,
512
+ statusColor(String(statusCode))
513
+ ];
514
+ if (durationMs !== void 0) {
515
+ parts.push(gray(`${durationMs}ms`));
516
+ }
517
+ console.log(parts.join(" "));
518
+ }
519
+ banner(message) {
520
+ console.log(bold(magenta(message)));
29
521
  }
30
522
  printHelp() {
31
- console.log(" Usage: docula [command] [arguments]");
523
+ console.log(
524
+ bold(
525
+ magenta(
526
+ "\n Docula \u{1F987} \u2014 Beautiful Website for Your Projects\n"
527
+ )
528
+ )
529
+ );
530
+ console.log(bold(cyan(" Usage:")));
531
+ console.log(" docula [command] [arguments]");
32
532
  console.log();
33
- console.log(" Commands:");
34
- console.log(" init Initialize a new project");
533
+ console.log(bold(cyan(" Commands:")));
534
+ console.log(` ${green("init")} Initialize a new project`);
535
+ console.log(
536
+ ` ${green("build")} Build the project. By default just npx docula will build the project if it finds a ./site folder`
537
+ );
35
538
  console.log(
36
- " build Build the project. By default just npx docula will build the project if it finds a ./site folder"
539
+ ` ${green("serve")} Serve the project as a local website`
37
540
  );
38
- console.log(" serve Serve the project as a local website");
39
- console.log(" help Print this help");
40
- console.log(" version Print the version");
541
+ console.log(` ${green("help")} Print this help`);
542
+ console.log(` ${green("version")} Print the version`);
41
543
  console.log();
42
- console.log(" Arguments init:");
544
+ console.log(bold(cyan(" Arguments init:")));
43
545
  console.log(
44
- " --typescript Generate TypeScript config file (docula.config.ts)"
546
+ ` ${yellow("--typescript")} Generate TypeScript config file (docula.config.ts)`
45
547
  );
46
548
  console.log(
47
- " -s, --site Set the path where site files are located"
549
+ ` ${yellow("-s, --site")} Set the path where site files are located`
48
550
  );
49
551
  console.log();
50
- console.log(" Arguments build:");
51
- console.log(" -w, --watch watch for changes and rebuild");
552
+ console.log(bold(cyan(" Arguments build:")));
553
+ console.log(
554
+ ` ${yellow("-w, --watch")} watch for changes and rebuild`
555
+ );
52
556
  console.log(
53
- " -s, --site Set the path where site files are located"
557
+ ` ${yellow("-c, --clean")} Clean the output directory before building`
54
558
  );
55
559
  console.log(
56
- " -o, --outputPath Set the output directory. Default is ./site/dist"
560
+ ` ${yellow("-s, --site")} Set the path where site files are located`
57
561
  );
58
- console.log(" -t, --templatePath Set the custom template to use");
59
562
  console.log(
60
- " -T, --template Set the built-in template name (e.g., modern, classic)"
563
+ ` ${yellow("-o, --output")} Set the output directory. Default is ./site/dist`
564
+ );
565
+ console.log(
566
+ ` ${yellow("-t, --templatePath")} Set the custom template to use`
567
+ );
568
+ console.log(
569
+ ` ${yellow("-T, --template")} Set the built-in template name (e.g., modern, classic)`
61
570
  );
62
571
  console.log();
63
- console.log(" Arguments serve:");
64
- console.log(" -p, --port Set the port number used with serve");
65
- console.log(" -w, --watch watch for changes and rebuild");
572
+ console.log(bold(cyan(" Arguments serve:")));
573
+ console.log(
574
+ ` ${yellow("-p, --port")} Set the port number used with serve`
575
+ );
576
+ console.log(
577
+ ` ${yellow("-b, --build")} Build the site before serving`
578
+ );
66
579
  console.log(
67
- " -s, --site Set the path where site files are located"
580
+ ` ${yellow("-w, --watch")} watch for changes and rebuild`
581
+ );
582
+ console.log(
583
+ ` ${yellow("-c, --clean")} Clean the output directory before building`
584
+ );
585
+ console.log(
586
+ ` ${yellow("-s, --site")} Set the path where site files are located`
68
587
  );
69
588
  }
70
589
  parseProcessArgv(argv) {
@@ -104,7 +623,9 @@ var DoculaConsole = class {
104
623
  template: "",
105
624
  output: "",
106
625
  watch: false,
107
- port: 3e3,
626
+ clean: false,
627
+ build: false,
628
+ port: void 0,
108
629
  typescript: false
109
630
  };
110
631
  for (let i = 0; i < argv.length; i++) {
@@ -129,6 +650,16 @@ var DoculaConsole = class {
129
650
  arguments_.watch = true;
130
651
  break;
131
652
  }
653
+ case "-c":
654
+ case "--clean": {
655
+ arguments_.clean = true;
656
+ break;
657
+ }
658
+ case "-b":
659
+ case "--build": {
660
+ arguments_.build = true;
661
+ break;
662
+ }
132
663
  case "-s":
133
664
  case "--site": {
134
665
  arguments_.sitePath = argv[i + 1];
@@ -160,6 +691,8 @@ var DoculaConsole = class {
160
691
  };
161
692
 
162
693
  // src/github.ts
694
+ import fs from "fs";
695
+ import path2 from "path";
163
696
  import process2 from "process";
164
697
  import { CacheableNet } from "@cacheable/net";
165
698
  var Github = class {
@@ -169,18 +702,32 @@ var Github = class {
169
702
  repo: ""
170
703
  };
171
704
  net;
172
- constructor(options) {
705
+ cacheConfig;
706
+ constructor(options, cacheConfig) {
173
707
  this.parseOptions(options);
174
708
  this.net = new CacheableNet();
709
+ if (cacheConfig) {
710
+ this.cacheConfig = cacheConfig;
711
+ }
175
712
  }
176
713
  async getData() {
714
+ if (this.cacheConfig && this.cacheConfig.ttl > 0) {
715
+ const cached = this.loadCache();
716
+ if (cached) {
717
+ return cached;
718
+ }
719
+ }
177
720
  const data = {
178
721
  releases: {},
179
722
  contributors: {}
180
723
  };
181
724
  data.releases = await this.getReleases();
182
725
  data.contributors = await this.getContributors();
183
- return data;
726
+ const result = data;
727
+ if (this.cacheConfig && this.cacheConfig.ttl > 0) {
728
+ this.saveCache(result);
729
+ }
730
+ return result;
184
731
  }
185
732
  // biome-ignore lint/suspicious/noExplicitAny: need to fix
186
733
  async getReleases() {
@@ -244,6 +791,41 @@ var Github = class {
244
791
  this.options.author = options.author;
245
792
  this.options.repo = options.repo;
246
793
  }
794
+ getCacheFilePath() {
795
+ if (!this.cacheConfig) {
796
+ throw new Error("Cache config is not set");
797
+ }
798
+ const cacheDir = path2.join(this.cacheConfig.cachePath, "github");
799
+ return path2.join(cacheDir, "github-data.json");
800
+ }
801
+ loadCache() {
802
+ try {
803
+ const cacheFile = this.getCacheFilePath();
804
+ if (!fs.existsSync(cacheFile)) {
805
+ return void 0;
806
+ }
807
+ const stat = fs.statSync(cacheFile);
808
+ const ageInSeconds = (Date.now() - stat.mtimeMs) / 1e3;
809
+ if (ageInSeconds > (this.cacheConfig?.ttl ?? 0)) {
810
+ return void 0;
811
+ }
812
+ const raw = fs.readFileSync(cacheFile, "utf8");
813
+ return JSON.parse(raw);
814
+ } catch {
815
+ return void 0;
816
+ }
817
+ }
818
+ saveCache(data) {
819
+ try {
820
+ const cacheFile = this.getCacheFilePath();
821
+ const cacheDir = path2.dirname(cacheFile);
822
+ if (!fs.existsSync(cacheDir)) {
823
+ fs.mkdirSync(cacheDir, { recursive: true });
824
+ }
825
+ fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2));
826
+ } catch {
827
+ }
828
+ }
247
829
  // biome-ignore lint/suspicious/noExplicitAny: need to fix
248
830
  addAnchorLink(data) {
249
831
  return data.map((release) => {
@@ -255,7 +837,7 @@ var Github = class {
255
837
  };
256
838
 
257
839
  // src/options.ts
258
- import path2 from "path";
840
+ import path3 from "path";
259
841
  import process3 from "process";
260
842
  var DoculaOptions = class {
261
843
  /**
@@ -269,15 +851,15 @@ var DoculaOptions = class {
269
851
  /**
270
852
  * Path to the output directory
271
853
  */
272
- outputPath = path2.join(process3.cwd(), "./dist");
854
+ output = path3.join(process3.cwd(), "./dist");
273
855
  /**
274
856
  * Path to the site directory
275
857
  */
276
- sitePath = path2.join(process3.cwd(), "./site");
858
+ sitePath = path3.join(process3.cwd(), "./site");
277
859
  /**
278
860
  * Path to the github repository
279
861
  */
280
- githubPath = "jaredwray/docula";
862
+ githubPath = "";
281
863
  /**
282
864
  * Site title
283
865
  */
@@ -294,10 +876,6 @@ var DoculaOptions = class {
294
876
  * Port to run the server
295
877
  */
296
878
  port = 3e3;
297
- /**
298
- * Single page website
299
- */
300
- singlePage = true;
301
879
  /**
302
880
  * Sections
303
881
  */
@@ -322,6 +900,66 @@ var DoculaOptions = class {
322
900
  * When true, generates llms.txt and llms-full.txt files for the built site.
323
901
  */
324
902
  enableLlmsTxt = true;
903
+ /**
904
+ * Override the default theme toggle. By default the site follows the system
905
+ * preference. Set to "light" or "dark" to use that theme when no user
906
+ * preference is stored in localStorage.
907
+ */
908
+ themeMode;
909
+ /**
910
+ * When true, automatically adds generated paths (e.g., .cache) to the
911
+ * site directory's .gitignore file if not already present.
912
+ */
913
+ autoUpdateIgnores = true;
914
+ /**
915
+ * File extensions to copy as assets from docs/ and changelog/ directories.
916
+ * Override in docula.config to customize.
917
+ */
918
+ /**
919
+ * Cookie-based authentication. When set, shows a Login/Logout button
920
+ * in the header based on whether a JWT cookie is present.
921
+ */
922
+ cookieAuth;
923
+ /**
924
+ * File extensions to copy as assets from docs/ and changelog/ directories.
925
+ * Override in docula.config to customize.
926
+ */
927
+ /**
928
+ * Cache settings. Controls caching of external data (e.g., GitHub API responses)
929
+ * in the .cache directory within the site path.
930
+ */
931
+ cache = {
932
+ github: {
933
+ ttl: 3600
934
+ }
935
+ };
936
+ /**
937
+ * File extensions to copy as assets from docs/ and changelog/ directories.
938
+ * Override in docula.config to customize.
939
+ */
940
+ allowedAssets = [
941
+ ".png",
942
+ ".jpg",
943
+ ".jpeg",
944
+ ".gif",
945
+ ".svg",
946
+ ".webp",
947
+ ".avif",
948
+ ".ico",
949
+ ".pdf",
950
+ ".zip",
951
+ ".tar",
952
+ ".gz",
953
+ ".mp4",
954
+ ".webm",
955
+ ".ogg",
956
+ ".mp3",
957
+ ".wav",
958
+ ".json",
959
+ ".xml",
960
+ ".csv",
961
+ ".txt"
962
+ ];
325
963
  constructor(options) {
326
964
  if (options) {
327
965
  this.parseOptions(options);
@@ -334,15 +972,15 @@ var DoculaOptions = class {
334
972
  }
335
973
  if (options.templatePath) {
336
974
  this.templatePath = options.templatePath;
337
- this.templatePath = path2.join(process3.cwd(), this.templatePath);
975
+ this.templatePath = path3.join(process3.cwd(), this.templatePath);
338
976
  }
339
- if (options.outputPath) {
340
- this.outputPath = options.outputPath;
341
- this.outputPath = path2.join(process3.cwd(), this.outputPath);
977
+ if (options.output) {
978
+ this.output = options.output;
979
+ this.output = path3.join(process3.cwd(), this.output);
342
980
  }
343
981
  if (options.sitePath) {
344
982
  this.sitePath = options.sitePath;
345
- this.sitePath = path2.join(process3.cwd(), this.sitePath);
983
+ this.sitePath = path3.join(process3.cwd(), this.sitePath);
346
984
  }
347
985
  if (options.githubPath) {
348
986
  this.githubPath = options.githubPath;
@@ -362,9 +1000,6 @@ var DoculaOptions = class {
362
1000
  if (options.port) {
363
1001
  this.port = options.port;
364
1002
  }
365
- if (options.singlePage !== void 0 && typeof options.singlePage === "boolean") {
366
- this.singlePage = options.singlePage;
367
- }
368
1003
  if (options.openApiUrl) {
369
1004
  this.openApiUrl = options.openApiUrl;
370
1005
  }
@@ -377,30 +1012,45 @@ var DoculaOptions = class {
377
1012
  if (options.enableLlmsTxt !== void 0 && typeof options.enableLlmsTxt === "boolean") {
378
1013
  this.enableLlmsTxt = options.enableLlmsTxt;
379
1014
  }
1015
+ if (options.themeMode !== void 0 && (options.themeMode === "light" || options.themeMode === "dark")) {
1016
+ this.themeMode = options.themeMode;
1017
+ }
1018
+ if (options.autoUpdateIgnores !== void 0 && typeof options.autoUpdateIgnores === "boolean") {
1019
+ this.autoUpdateIgnores = options.autoUpdateIgnores;
1020
+ }
1021
+ if (options.cache && typeof options.cache === "object" && options.cache.github !== null && typeof options.cache.github === "object" && typeof options.cache.github.ttl === "number") {
1022
+ this.cache = options.cache;
1023
+ }
1024
+ if (options.allowedAssets && Array.isArray(options.allowedAssets)) {
1025
+ this.allowedAssets = options.allowedAssets;
1026
+ }
1027
+ if (options.cookieAuth && typeof options.cookieAuth === "object" && typeof options.cookieAuth.loginUrl === "string") {
1028
+ this.cookieAuth = options.cookieAuth;
1029
+ }
380
1030
  }
381
1031
  };
382
1032
 
383
1033
  // src/template-resolver.ts
384
- import fs from "fs";
385
- import path3 from "path";
1034
+ import fs2 from "fs";
1035
+ import path4 from "path";
386
1036
  function getBuiltInTemplatesDir() {
387
- return path3.join(import.meta.url, "../../templates").replace("file:", "");
1037
+ return path4.join(import.meta.url, "../../templates").replace("file:", "");
388
1038
  }
389
1039
  function listBuiltInTemplates() {
390
1040
  const templatesDir = getBuiltInTemplatesDir();
391
- if (!fs.existsSync(templatesDir)) {
1041
+ if (!fs2.existsSync(templatesDir)) {
392
1042
  return [];
393
1043
  }
394
- return fs.readdirSync(templatesDir).filter(
395
- (entry) => fs.statSync(path3.join(templatesDir, entry)).isDirectory()
1044
+ return fs2.readdirSync(templatesDir).filter(
1045
+ (entry) => fs2.statSync(path4.join(templatesDir, entry)).isDirectory()
396
1046
  );
397
1047
  }
398
1048
  function resolveTemplatePath(templatePath, templateName) {
399
1049
  if (templatePath) {
400
1050
  return templatePath;
401
1051
  }
402
- const resolvedPath = path3.join(getBuiltInTemplatesDir(), templateName);
403
- if (!fs.existsSync(resolvedPath)) {
1052
+ const resolvedPath = path4.join(getBuiltInTemplatesDir(), templateName);
1053
+ if (!fs2.existsSync(resolvedPath)) {
404
1054
  const available = listBuiltInTemplates();
405
1055
  throw new Error(
406
1056
  `Built-in template "${templateName}" not found. Available templates: ${available.join(", ")}`
@@ -427,8 +1077,9 @@ var DoculaBuilder = class {
427
1077
  async build() {
428
1078
  const startTime = Date.now();
429
1079
  this.validateOptions(this.options);
430
- const resolvedTemplatePath = resolveTemplatePath(
431
- this.options.templatePath,
1080
+ const resolvedTemplatePath = this.mergeTemplateOverrides(
1081
+ resolveTemplatePath(this.options.templatePath, this.options.template),
1082
+ this.options.sitePath,
432
1083
  this.options.template
433
1084
  );
434
1085
  const doculaData = {
@@ -437,16 +1088,20 @@ var DoculaBuilder = class {
437
1088
  siteDescription: this.options.siteDescription,
438
1089
  sitePath: this.options.sitePath,
439
1090
  templatePath: resolvedTemplatePath,
440
- outputPath: this.options.outputPath,
1091
+ output: this.options.output,
441
1092
  githubPath: this.options.githubPath,
442
1093
  sections: this.options.sections,
443
1094
  openApiUrl: this.options.openApiUrl,
444
- homePage: this.options.homePage
1095
+ homePage: this.options.homePage,
1096
+ themeMode: this.options.themeMode,
1097
+ cookieAuth: this.options.cookieAuth
445
1098
  };
446
- if (!doculaData.openApiUrl && fs2.existsSync(`${doculaData.sitePath}/api/swagger.json`)) {
1099
+ if (!doculaData.openApiUrl && fs3.existsSync(`${doculaData.sitePath}/api/swagger.json`)) {
447
1100
  doculaData.openApiUrl = "/api/swagger.json";
448
1101
  }
449
- doculaData.github = await this.getGithubData(this.options.githubPath);
1102
+ if (this.options.githubPath) {
1103
+ doculaData.github = await this.getGithubData(this.options.githubPath);
1104
+ }
450
1105
  doculaData.documents = this.getDocuments(
451
1106
  `${doculaData.sitePath}/docs`,
452
1107
  doculaData
@@ -491,63 +1146,106 @@ var DoculaBuilder = class {
491
1146
  doculaData.hasApi = Boolean(
492
1147
  doculaData.openApiUrl && doculaData.templates?.api
493
1148
  );
1149
+ this._console.step("Building pages...");
494
1150
  if (!this.options.homePage && doculaData.hasDocuments) {
495
1151
  await this.buildDocsHomePage(doculaData);
1152
+ this._console.fileBuilt("index.html");
1153
+ } else if (!this.options.homePage && !doculaData.hasDocuments) {
1154
+ this._console.error(
1155
+ "homePage is set to false but no documents were found in the docs directory. Add documents to the docs/ folder or set homePage to true."
1156
+ );
496
1157
  } else {
497
1158
  await this.buildIndexPage(doculaData);
1159
+ this._console.fileBuilt("index.html");
498
1160
  }
499
1161
  await this.buildSiteMapPage(doculaData);
1162
+ this._console.fileBuilt("sitemap.xml");
500
1163
  await this.buildRobotsPage(this.options);
1164
+ this._console.fileBuilt("robots.txt");
501
1165
  if (doculaData.hasDocuments) {
1166
+ this._console.step("Building documentation pages...");
502
1167
  await this.buildDocsPages(doculaData);
1168
+ for (const document of doculaData.documents ?? []) {
1169
+ this._console.fileBuilt(document.urlPath);
1170
+ }
503
1171
  }
504
1172
  if (doculaData.hasApi) {
1173
+ this._console.step("Building API page...");
505
1174
  await this.buildApiPage(doculaData);
1175
+ this._console.fileBuilt("api/index.html");
506
1176
  }
507
1177
  if (doculaData.hasChangelog) {
1178
+ this._console.step("Building changelog...");
508
1179
  await this.buildChangelogPage(doculaData);
1180
+ this._console.fileBuilt("changelog/index.html");
509
1181
  await this.buildChangelogEntryPages(doculaData);
1182
+ for (const entry of doculaData.changelogEntries ?? []) {
1183
+ this._console.fileBuilt(`changelog/${entry.slug}/index.html`);
1184
+ }
510
1185
  }
511
1186
  const siteRelativePath = this.options.sitePath;
512
- if (fs2.existsSync(`${siteRelativePath}/favicon.ico`)) {
513
- await fs2.promises.copyFile(
1187
+ this._console.step("Copying assets...");
1188
+ if (fs3.existsSync(`${siteRelativePath}/favicon.ico`)) {
1189
+ await fs3.promises.copyFile(
514
1190
  `${siteRelativePath}/favicon.ico`,
515
- `${this.options.outputPath}/favicon.ico`
1191
+ `${this.options.output}/favicon.ico`
516
1192
  );
1193
+ this._console.fileCopied("favicon.ico");
517
1194
  }
518
- if (fs2.existsSync(`${siteRelativePath}/logo.svg`)) {
519
- await fs2.promises.copyFile(
1195
+ if (fs3.existsSync(`${siteRelativePath}/logo.svg`)) {
1196
+ await fs3.promises.copyFile(
520
1197
  `${siteRelativePath}/logo.svg`,
521
- `${this.options.outputPath}/logo.svg`
1198
+ `${this.options.output}/logo.svg`
522
1199
  );
1200
+ this._console.fileCopied("logo.svg");
523
1201
  }
524
- if (fs2.existsSync(`${siteRelativePath}/logo_horizontal.png`)) {
525
- await fs2.promises.copyFile(
1202
+ if (fs3.existsSync(`${siteRelativePath}/logo_horizontal.png`)) {
1203
+ await fs3.promises.copyFile(
526
1204
  `${siteRelativePath}/logo_horizontal.png`,
527
- `${this.options.outputPath}/logo_horizontal.png`
1205
+ `${this.options.output}/logo_horizontal.png`
528
1206
  );
1207
+ this._console.fileCopied("logo_horizontal.png");
529
1208
  }
530
- if (fs2.existsSync(`${resolvedTemplatePath}/css`)) {
1209
+ if (fs3.existsSync(`${resolvedTemplatePath}/css`)) {
531
1210
  this.copyDirectory(
532
1211
  `${resolvedTemplatePath}/css`,
533
- `${this.options.outputPath}/css`
1212
+ `${this.options.output}/css`
534
1213
  );
1214
+ this._console.fileCopied("css/");
535
1215
  }
536
- if (fs2.existsSync(`${siteRelativePath}/variables.css`)) {
537
- await fs2.promises.copyFile(
1216
+ if (fs3.existsSync(`${resolvedTemplatePath}/js`)) {
1217
+ this.copyDirectory(
1218
+ `${resolvedTemplatePath}/js`,
1219
+ `${this.options.output}/js`
1220
+ );
1221
+ this._console.fileCopied("js/");
1222
+ }
1223
+ if (fs3.existsSync(`${siteRelativePath}/variables.css`)) {
1224
+ await fs3.promises.copyFile(
538
1225
  `${siteRelativePath}/variables.css`,
539
- `${this.options.outputPath}/css/variables.css`
1226
+ `${this.options.output}/css/variables.css`
540
1227
  );
1228
+ this._console.fileCopied("css/variables.css");
1229
+ }
1230
+ this.copyPublicFolder(siteRelativePath, this.options.output);
1231
+ this.copyContentAssets(
1232
+ `${doculaData.sitePath}/changelog`,
1233
+ `${this.options.output}/changelog`
1234
+ );
1235
+ if (doculaData.documents?.length) {
1236
+ this.copyDocumentSiblingAssets(doculaData);
1237
+ }
1238
+ if (this.options.enableLlmsTxt) {
1239
+ this._console.step("Building LLM files...");
541
1240
  }
542
- this.copyPublicFolder(siteRelativePath, this.options.outputPath);
543
1241
  await this.buildLlmsFiles(doculaData);
544
1242
  const endTime = Date.now();
545
1243
  const executionTime = endTime - startTime;
546
- this._console.log(`Build completed in ${executionTime}ms`);
1244
+ this._console.success(`Build completed in ${executionTime}ms`);
547
1245
  }
548
1246
  validateOptions(options) {
549
- if (options.githubPath.length < 3) {
550
- throw new Error("No github options provided");
1247
+ if (options.githubPath && !options.githubPath.includes("/")) {
1248
+ throw new Error("githubPath must be in 'owner/repo' format");
551
1249
  }
552
1250
  if (options.siteDescription.length < 3) {
553
1251
  throw new Error("No site description options provided");
@@ -565,14 +1263,21 @@ var DoculaBuilder = class {
565
1263
  author: paths[0],
566
1264
  repo: paths[1]
567
1265
  };
568
- const github = new Github(options);
1266
+ let cacheConfig;
1267
+ if (this._options.cache.github.ttl > 0) {
1268
+ cacheConfig = {
1269
+ cachePath: path5.join(this._options.sitePath, ".cache"),
1270
+ ttl: this._options.cache.github.ttl
1271
+ };
1272
+ }
1273
+ const github = new Github(options, cacheConfig);
569
1274
  return github.getData();
570
1275
  }
571
1276
  async getTemplates(templatePath, hasDocuments, hasChangelog = false) {
572
1277
  const templates = {
573
1278
  home: ""
574
1279
  };
575
- if (fs2.existsSync(templatePath)) {
1280
+ if (fs3.existsSync(templatePath)) {
576
1281
  const home = await this.getTemplateFile(templatePath, "home");
577
1282
  if (home) {
578
1283
  templates.home = home;
@@ -598,9 +1303,9 @@ var DoculaBuilder = class {
598
1303
  }
599
1304
  return templates;
600
1305
  }
601
- async getTemplateFile(path6, name) {
1306
+ async getTemplateFile(path7, name) {
602
1307
  let result;
603
- const files = await fs2.promises.readdir(path6);
1308
+ const files = await fs3.promises.readdir(path7);
604
1309
  for (const file of files) {
605
1310
  const fileName = file.split(".");
606
1311
  if (fileName[0].toString().toLowerCase() === name.toLowerCase()) {
@@ -612,13 +1317,13 @@ var DoculaBuilder = class {
612
1317
  }
613
1318
  async buildRobotsPage(options) {
614
1319
  const { sitePath } = options;
615
- const { outputPath } = options;
616
- const robotsPath = `${outputPath}/robots.txt`;
617
- await fs2.promises.mkdir(outputPath, { recursive: true });
618
- await (fs2.existsSync(`${sitePath}/robots.txt`) ? fs2.promises.copyFile(`${sitePath}/robots.txt`, robotsPath) : fs2.promises.writeFile(robotsPath, "User-agent: *\nDisallow:"));
1320
+ const { output } = options;
1321
+ const robotsPath = `${output}/robots.txt`;
1322
+ await fs3.promises.mkdir(output, { recursive: true });
1323
+ await (fs3.existsSync(`${sitePath}/robots.txt`) ? fs3.promises.copyFile(`${sitePath}/robots.txt`, robotsPath) : fs3.promises.writeFile(robotsPath, "User-agent: *\nDisallow:"));
619
1324
  }
620
1325
  async buildSiteMapPage(data) {
621
- const sitemapPath = `${data.outputPath}/sitemap.xml`;
1326
+ const sitemapPath = `${data.output}/sitemap.xml`;
622
1327
  const urls = [{ url: data.siteUrl }];
623
1328
  if (data.openApiUrl && data.templates?.api) {
624
1329
  urls.push({ url: `${data.siteUrl}/api` });
@@ -646,16 +1351,16 @@ var DoculaBuilder = class {
646
1351
  xml += "</url>";
647
1352
  }
648
1353
  xml += "</urlset>";
649
- await fs2.promises.mkdir(data.outputPath, { recursive: true });
650
- await fs2.promises.writeFile(sitemapPath, xml, "utf8");
1354
+ await fs3.promises.mkdir(data.output, { recursive: true });
1355
+ await fs3.promises.writeFile(sitemapPath, xml, "utf8");
651
1356
  }
652
1357
  async buildLlmsFiles(data) {
653
1358
  if (!this.options.enableLlmsTxt) {
654
1359
  return;
655
1360
  }
656
- await fs2.promises.mkdir(data.outputPath, { recursive: true });
657
- const llmsOutputPath = `${data.outputPath}/llms.txt`;
658
- const llmsFullOutputPath = `${data.outputPath}/llms-full.txt`;
1361
+ await fs3.promises.mkdir(data.output, { recursive: true });
1362
+ const llmsOutputPath = `${data.output}/llms.txt`;
1363
+ const llmsFullOutputPath = `${data.output}/llms-full.txt`;
659
1364
  const llmsOverrideContent = await this.getSafeSiteOverrideFileContent(
660
1365
  data.sitePath,
661
1366
  "llms.txt"
@@ -665,21 +1370,23 @@ var DoculaBuilder = class {
665
1370
  "llms-full.txt"
666
1371
  );
667
1372
  if (llmsOverrideContent !== void 0) {
668
- await fs2.promises.writeFile(llmsOutputPath, llmsOverrideContent, "utf8");
1373
+ await fs3.promises.writeFile(llmsOutputPath, llmsOverrideContent, "utf8");
669
1374
  } else {
670
1375
  const llmsContent = this.generateLlmsIndexContent(data);
671
- await fs2.promises.writeFile(llmsOutputPath, llmsContent, "utf8");
1376
+ await fs3.promises.writeFile(llmsOutputPath, llmsContent, "utf8");
672
1377
  }
1378
+ this._console.fileBuilt("llms.txt");
673
1379
  if (llmsFullOverrideContent !== void 0) {
674
- await fs2.promises.writeFile(
1380
+ await fs3.promises.writeFile(
675
1381
  llmsFullOutputPath,
676
1382
  llmsFullOverrideContent,
677
1383
  "utf8"
678
1384
  );
679
1385
  } else {
680
1386
  const llmsFullContent = await this.generateLlmsFullContent(data);
681
- await fs2.promises.writeFile(llmsFullOutputPath, llmsFullContent, "utf8");
1387
+ await fs3.promises.writeFile(llmsFullOutputPath, llmsFullContent, "utf8");
682
1388
  }
1389
+ this._console.fileBuilt("llms-full.txt");
683
1390
  }
684
1391
  generateLlmsIndexContent(data) {
685
1392
  const lines = [];
@@ -846,17 +1553,17 @@ var DoculaBuilder = class {
846
1553
  return void 0;
847
1554
  }
848
1555
  const normalizedPath = openApiPathWithoutQuery.startsWith("/") ? openApiPathWithoutQuery.slice(1) : openApiPathWithoutQuery;
849
- return path4.join(data.sitePath, normalizedPath);
1556
+ return path5.join(data.sitePath, normalizedPath);
850
1557
  }
851
1558
  async getSafeSiteOverrideFileContent(sitePath, fileName) {
852
- const resolvedSitePath = path4.resolve(sitePath);
853
- const candidatePath = path4.resolve(sitePath, fileName);
1559
+ const resolvedSitePath = path5.resolve(sitePath);
1560
+ const candidatePath = path5.resolve(sitePath, fileName);
854
1561
  if (!this.isPathWithinBasePath(candidatePath, resolvedSitePath)) {
855
1562
  return void 0;
856
1563
  }
857
1564
  let candidateStats;
858
1565
  try {
859
- candidateStats = await fs2.promises.lstat(candidatePath);
1566
+ candidateStats = await fs3.promises.lstat(candidatePath);
860
1567
  } catch {
861
1568
  return void 0;
862
1569
  }
@@ -866,29 +1573,29 @@ var DoculaBuilder = class {
866
1573
  let realSitePath;
867
1574
  let realCandidatePath;
868
1575
  try {
869
- realSitePath = await fs2.promises.realpath(resolvedSitePath);
870
- realCandidatePath = await fs2.promises.realpath(candidatePath);
1576
+ realSitePath = await fs3.promises.realpath(resolvedSitePath);
1577
+ realCandidatePath = await fs3.promises.realpath(candidatePath);
871
1578
  } catch {
872
1579
  return void 0;
873
1580
  }
874
1581
  if (!this.isPathWithinBasePath(realCandidatePath, realSitePath)) {
875
1582
  return void 0;
876
1583
  }
877
- return fs2.promises.readFile(realCandidatePath, "utf8");
1584
+ return fs3.promises.readFile(realCandidatePath, "utf8");
878
1585
  }
879
1586
  async getSafeLocalOpenApiSpec(data) {
880
1587
  const localOpenApiPath = this.resolveLocalOpenApiPath(data);
881
1588
  if (!localOpenApiPath) {
882
1589
  return void 0;
883
1590
  }
884
- const resolvedSitePath = path4.resolve(data.sitePath);
885
- const resolvedLocalOpenApiPath = path4.resolve(localOpenApiPath);
1591
+ const resolvedSitePath = path5.resolve(data.sitePath);
1592
+ const resolvedLocalOpenApiPath = path5.resolve(localOpenApiPath);
886
1593
  if (!this.isPathWithinBasePath(resolvedLocalOpenApiPath, resolvedSitePath)) {
887
1594
  return void 0;
888
1595
  }
889
1596
  let localOpenApiStats;
890
1597
  try {
891
- localOpenApiStats = await fs2.promises.lstat(resolvedLocalOpenApiPath);
1598
+ localOpenApiStats = await fs3.promises.lstat(resolvedLocalOpenApiPath);
892
1599
  } catch {
893
1600
  return void 0;
894
1601
  }
@@ -898,8 +1605,8 @@ var DoculaBuilder = class {
898
1605
  let realSitePath;
899
1606
  let realLocalOpenApiPath;
900
1607
  try {
901
- realSitePath = await fs2.promises.realpath(resolvedSitePath);
902
- realLocalOpenApiPath = await fs2.promises.realpath(
1608
+ realSitePath = await fs3.promises.realpath(resolvedSitePath);
1609
+ realLocalOpenApiPath = await fs3.promises.realpath(
903
1610
  resolvedLocalOpenApiPath
904
1611
  );
905
1612
  } catch {
@@ -908,26 +1615,26 @@ var DoculaBuilder = class {
908
1615
  if (!this.isPathWithinBasePath(realLocalOpenApiPath, realSitePath)) {
909
1616
  return void 0;
910
1617
  }
911
- const localOpenApiContent = (await fs2.promises.readFile(realLocalOpenApiPath, "utf8")).trim();
1618
+ const localOpenApiContent = (await fs3.promises.readFile(realLocalOpenApiPath, "utf8")).trim();
912
1619
  return {
913
1620
  sourcePath: realLocalOpenApiPath,
914
1621
  content: localOpenApiContent
915
1622
  };
916
1623
  }
917
1624
  isPathWithinBasePath(candidatePath, basePath) {
918
- const relativePath = path4.relative(
919
- path4.resolve(basePath),
920
- path4.resolve(candidatePath)
1625
+ const relativePath = path5.relative(
1626
+ path5.resolve(basePath),
1627
+ path5.resolve(candidatePath)
921
1628
  );
922
- return relativePath === "" || !relativePath.startsWith("..") && !path4.isAbsolute(relativePath);
1629
+ return relativePath === "" || !relativePath.startsWith("..") && !path5.isAbsolute(relativePath);
923
1630
  }
924
1631
  toPosixPath(filePath) {
925
- return filePath.replaceAll(path4.sep, path4.posix.sep);
1632
+ return filePath.replaceAll(path5.sep, path5.posix.sep);
926
1633
  }
927
1634
  async buildIndexPage(data) {
928
1635
  if (data.templates) {
929
- const indexPath = `${data.outputPath}/index.html`;
930
- await fs2.promises.mkdir(data.outputPath, { recursive: true });
1636
+ const indexPath = `${data.output}/index.html`;
1637
+ await fs3.promises.mkdir(data.output, { recursive: true });
931
1638
  const indexTemplate = `${data.templatePath}/${data.templates.home}`;
932
1639
  let content;
933
1640
  if (!data.hasDocuments) {
@@ -939,7 +1646,7 @@ var DoculaBuilder = class {
939
1646
  { ...data, content, announcement },
940
1647
  data.templatePath
941
1648
  );
942
- await fs2.promises.writeFile(indexPath, indexContent, "utf8");
1649
+ await fs3.promises.writeFile(indexPath, indexContent, "utf8");
943
1650
  } else {
944
1651
  throw new Error("No templates found");
945
1652
  }
@@ -951,8 +1658,8 @@ var DoculaBuilder = class {
951
1658
  if (!data.documents?.length) {
952
1659
  throw new Error("No documents found for homePage");
953
1660
  }
954
- const indexPath = `${data.outputPath}/index.html`;
955
- await fs2.promises.mkdir(data.outputPath, { recursive: true });
1661
+ const indexPath = `${data.output}/index.html`;
1662
+ await fs3.promises.mkdir(data.output, { recursive: true });
956
1663
  const documentsTemplate = `${data.templatePath}/${data.templates.docPage}`;
957
1664
  const firstDocument = data.documents[0];
958
1665
  if (!data.sidebarItems) {
@@ -963,12 +1670,12 @@ var DoculaBuilder = class {
963
1670
  { ...data, ...firstDocument },
964
1671
  data.templatePath
965
1672
  );
966
- await fs2.promises.writeFile(indexPath, documentContent, "utf8");
1673
+ await fs3.promises.writeFile(indexPath, documentContent, "utf8");
967
1674
  }
968
1675
  async buildReadmeSection(data) {
969
1676
  let htmlReadme = "";
970
- if (fs2.existsSync(`${data.sitePath}/README.md`)) {
971
- const readmeContent = fs2.readFileSync(
1677
+ if (fs3.existsSync(`${data.sitePath}/README.md`)) {
1678
+ const readmeContent = fs3.readFileSync(
972
1679
  `${data.sitePath}/README.md`,
973
1680
  "utf8"
974
1681
  );
@@ -978,8 +1685,8 @@ var DoculaBuilder = class {
978
1685
  }
979
1686
  async buildAnnouncementSection(data) {
980
1687
  const announcementPath = `${data.sitePath}/announcement.md`;
981
- if (fs2.existsSync(announcementPath)) {
982
- const announcementContent = fs2.readFileSync(announcementPath, "utf8");
1688
+ if (fs3.existsSync(announcementPath)) {
1689
+ const announcementContent = fs3.readFileSync(announcementPath, "utf8");
983
1690
  return new Writr(announcementContent).render();
984
1691
  }
985
1692
  return void 0;
@@ -987,20 +1694,20 @@ var DoculaBuilder = class {
987
1694
  async buildDocsPages(data) {
988
1695
  if (data.templates && data.documents?.length) {
989
1696
  const documentsTemplate = `${data.templatePath}/${data.templates.docPage}`;
990
- await fs2.promises.mkdir(`${data.outputPath}/docs`, { recursive: true });
1697
+ await fs3.promises.mkdir(`${data.output}/docs`, { recursive: true });
991
1698
  data.sidebarItems = this.generateSidebarItems(data);
992
1699
  const promises = data.documents.map(async (document) => {
993
1700
  const folder = document.urlPath.split("/").slice(0, -1).join("/");
994
- await fs2.promises.mkdir(`${data.outputPath}/${folder}`, {
1701
+ await fs3.promises.mkdir(`${data.output}/${folder}`, {
995
1702
  recursive: true
996
1703
  });
997
- const slug = `${data.outputPath}${document.urlPath}`;
1704
+ const slug = `${data.output}${document.urlPath}`;
998
1705
  const documentContent = await this._ecto.renderFromFile(
999
1706
  documentsTemplate,
1000
1707
  { ...data, ...document },
1001
1708
  data.templatePath
1002
1709
  );
1003
- return fs2.promises.writeFile(slug, documentContent, "utf8");
1710
+ return fs3.promises.writeFile(slug, documentContent, "utf8");
1004
1711
  });
1005
1712
  await Promise.all(promises);
1006
1713
  } else {
@@ -1011,33 +1718,56 @@ var DoculaBuilder = class {
1011
1718
  if (!data.openApiUrl || !data.templates?.api) {
1012
1719
  return;
1013
1720
  }
1014
- const apiPath = `${data.outputPath}/api/index.html`;
1015
- const apiOutputPath = `${data.outputPath}/api`;
1016
- await fs2.promises.mkdir(apiOutputPath, { recursive: true });
1721
+ const apiPath = `${data.output}/api/index.html`;
1722
+ const apiOutputPath = `${data.output}/api`;
1723
+ await fs3.promises.mkdir(apiOutputPath, { recursive: true });
1017
1724
  const swaggerSource = `${data.sitePath}/api/swagger.json`;
1018
- if (fs2.existsSync(swaggerSource)) {
1019
- await fs2.promises.copyFile(
1725
+ if (fs3.existsSync(swaggerSource)) {
1726
+ await fs3.promises.copyFile(
1020
1727
  swaggerSource,
1021
1728
  `${apiOutputPath}/swagger.json`
1022
1729
  );
1023
1730
  }
1731
+ let apiSpec;
1732
+ const localSpec = await this.getSafeLocalOpenApiSpec(data);
1733
+ if (localSpec) {
1734
+ apiSpec = parseOpenApiSpec(localSpec.content);
1735
+ } else if (data.openApiUrl && this.isRemoteUrl(data.openApiUrl)) {
1736
+ try {
1737
+ const response = await fetch(data.openApiUrl);
1738
+ const specContent = await response.text();
1739
+ apiSpec = parseOpenApiSpec(specContent);
1740
+ } catch {
1741
+ }
1742
+ }
1743
+ if (apiSpec) {
1744
+ apiSpec.info.description = new Writr(
1745
+ apiSpec.info.description
1746
+ ).renderSync();
1747
+ for (const group of apiSpec.groups) {
1748
+ group.description = new Writr(group.description).renderSync();
1749
+ for (const op of group.operations) {
1750
+ op.description = new Writr(op.description).renderSync();
1751
+ }
1752
+ }
1753
+ }
1024
1754
  const apiTemplate = `${data.templatePath}/${data.templates.api}`;
1025
1755
  const apiContent = await this._ecto.renderFromFile(
1026
1756
  apiTemplate,
1027
- { ...data, specUrl: data.openApiUrl },
1757
+ { ...data, specUrl: data.openApiUrl, apiSpec },
1028
1758
  data.templatePath
1029
1759
  );
1030
- await fs2.promises.writeFile(apiPath, apiContent, "utf8");
1760
+ await fs3.promises.writeFile(apiPath, apiContent, "utf8");
1031
1761
  }
1032
1762
  getChangelogEntries(changelogPath) {
1033
1763
  const entries = [];
1034
- if (!fs2.existsSync(changelogPath)) {
1764
+ if (!fs3.existsSync(changelogPath)) {
1035
1765
  return entries;
1036
1766
  }
1037
- const files = fs2.readdirSync(changelogPath);
1767
+ const files = fs3.readdirSync(changelogPath);
1038
1768
  for (const file of files) {
1039
1769
  const filePath = `${changelogPath}/${file}`;
1040
- const stats = fs2.statSync(filePath);
1770
+ const stats = fs3.statSync(filePath);
1041
1771
  if (stats.isFile() && (file.endsWith(".md") || file.endsWith(".mdx"))) {
1042
1772
  const entry = this.parseChangelogEntry(filePath);
1043
1773
  entries.push(entry);
@@ -1060,11 +1790,11 @@ var DoculaBuilder = class {
1060
1790
  return entries;
1061
1791
  }
1062
1792
  parseChangelogEntry(filePath) {
1063
- const fileContent = fs2.readFileSync(filePath, "utf8");
1793
+ const fileContent = fs3.readFileSync(filePath, "utf8");
1064
1794
  const writr = new Writr(fileContent);
1065
1795
  const matterData = writr.frontMatter;
1066
1796
  const markdownContent = writr.body;
1067
- const fileName = path4.basename(filePath, path4.extname(filePath));
1797
+ const fileName = path5.basename(filePath, path5.extname(filePath));
1068
1798
  const slug = fileName.replace(/^\d{4}-\d{2}-\d{2}-/, "");
1069
1799
  const isMdx = filePath.endsWith(".mdx");
1070
1800
  const tag = matterData.tag;
@@ -1144,16 +1874,16 @@ var DoculaBuilder = class {
1144
1874
  if (!data.hasChangelog || !data.templates?.changelog) {
1145
1875
  return;
1146
1876
  }
1147
- const changelogOutputPath = `${data.outputPath}/changelog`;
1877
+ const changelogOutputPath = `${data.output}/changelog`;
1148
1878
  const changelogIndexPath = `${changelogOutputPath}/index.html`;
1149
- await fs2.promises.mkdir(changelogOutputPath, { recursive: true });
1879
+ await fs3.promises.mkdir(changelogOutputPath, { recursive: true });
1150
1880
  const changelogTemplate = `${data.templatePath}/${data.templates.changelog}`;
1151
1881
  const changelogContent = await this._ecto.renderFromFile(
1152
1882
  changelogTemplate,
1153
1883
  { ...data, entries: data.changelogEntries },
1154
1884
  data.templatePath
1155
1885
  );
1156
- await fs2.promises.writeFile(changelogIndexPath, changelogContent, "utf8");
1886
+ await fs3.promises.writeFile(changelogIndexPath, changelogContent, "utf8");
1157
1887
  }
1158
1888
  async buildChangelogEntryPages(data) {
1159
1889
  if (!data.hasChangelog || !data.templates?.changelogEntry || !data.changelogEntries?.length) {
@@ -1161,15 +1891,15 @@ var DoculaBuilder = class {
1161
1891
  }
1162
1892
  const entryTemplate = `${data.templatePath}/${data.templates.changelogEntry}`;
1163
1893
  const promises = data.changelogEntries.map(async (entry) => {
1164
- const entryOutputPath = `${data.outputPath}/changelog/${entry.slug}`;
1165
- await fs2.promises.mkdir(entryOutputPath, { recursive: true });
1894
+ const entryOutputPath = `${data.output}/changelog/${entry.slug}`;
1895
+ await fs3.promises.mkdir(entryOutputPath, { recursive: true });
1166
1896
  const entryContent = await this._ecto.renderFromFile(
1167
1897
  entryTemplate,
1168
1898
  { ...data, ...entry, entries: data.changelogEntries },
1169
1899
  data.templatePath
1170
1900
  );
1171
1901
  const entryFilePath = `${entryOutputPath}/index.html`;
1172
- return fs2.promises.writeFile(entryFilePath, entryContent, "utf8");
1902
+ return fs3.promises.writeFile(entryFilePath, entryContent, "utf8");
1173
1903
  });
1174
1904
  await Promise.all(promises);
1175
1905
  }
@@ -1225,7 +1955,7 @@ var DoculaBuilder = class {
1225
1955
  }
1226
1956
  getDocuments(sitePath, doculaData) {
1227
1957
  let documents = [];
1228
- if (fs2.existsSync(sitePath)) {
1958
+ if (fs3.existsSync(sitePath)) {
1229
1959
  documents = this.getDocumentInDirectory(sitePath);
1230
1960
  doculaData.sections = this.getSections(sitePath, this.options);
1231
1961
  for (const section of doculaData.sections) {
@@ -1238,12 +1968,12 @@ var DoculaBuilder = class {
1238
1968
  }
1239
1969
  getDocumentInDirectory(sitePath) {
1240
1970
  const documents = [];
1241
- const documentList = fs2.readdirSync(sitePath);
1971
+ const documentList = fs3.readdirSync(sitePath);
1242
1972
  if (documentList.length > 0) {
1243
1973
  for (const document of documentList) {
1244
1974
  const documentPath = `${sitePath}/${document}`;
1245
- const stats = fs2.statSync(documentPath);
1246
- if (stats.isFile()) {
1975
+ const stats = fs3.statSync(documentPath);
1976
+ if (stats.isFile() && (document.endsWith(".md") || document.endsWith(".mdx"))) {
1247
1977
  const documentData = this.parseDocumentData(documentPath);
1248
1978
  documents.push(documentData);
1249
1979
  }
@@ -1256,13 +1986,13 @@ var DoculaBuilder = class {
1256
1986
  }
1257
1987
  getSections(sitePath, doculaOptions) {
1258
1988
  const sections = [];
1259
- if (fs2.existsSync(sitePath)) {
1260
- const documentList = fs2.readdirSync(sitePath);
1989
+ if (fs3.existsSync(sitePath)) {
1990
+ const documentList = fs3.readdirSync(sitePath);
1261
1991
  if (documentList.length > 0) {
1262
1992
  for (const document of documentList) {
1263
1993
  const documentPath = `${sitePath}/${document}`;
1264
- const stats = fs2.statSync(documentPath);
1265
- if (stats.isDirectory()) {
1994
+ const stats = fs3.statSync(documentPath);
1995
+ if (stats.isDirectory() && this.directoryContainsMarkdown(documentPath)) {
1266
1996
  const section = {
1267
1997
  name: document.replaceAll("-", " ").replaceAll(/\b\w/g, (l) => l.toUpperCase()),
1268
1998
  path: document
@@ -1292,7 +2022,7 @@ var DoculaBuilder = class {
1292
2022
  return section;
1293
2023
  }
1294
2024
  parseDocumentData(documentPath) {
1295
- const documentContent = fs2.readFileSync(documentPath, "utf8");
2025
+ const documentContent = fs3.readFileSync(documentPath, "utf8");
1296
2026
  const writr = new Writr(documentContent);
1297
2027
  const matterData = writr.frontMatter;
1298
2028
  let markdownContent = writr.body;
@@ -1310,9 +2040,13 @@ var DoculaBuilder = class {
1310
2040
  }
1311
2041
  }
1312
2042
  if (!this.hasTableOfContents(markdownContent)) {
1313
- markdownContent = `## Table of Contents
2043
+ const h2Matches = markdownContent.match(/^## /gm);
2044
+ if (h2Matches && h2Matches.length >= 2) {
2045
+ const firstH2 = markdownContent.search(/^## /m);
2046
+ markdownContent = `${markdownContent.slice(0, firstH2)}## Table of Contents
1314
2047
 
1315
- ${markdownContent}`;
2048
+ ${markdownContent.slice(firstH2)}`;
2049
+ }
1316
2050
  }
1317
2051
  return {
1318
2052
  title: matterData.title,
@@ -1339,67 +2073,260 @@ ${markdownContent}`;
1339
2073
  const htmlHeading = /<h[1-6][^>]*>\s*(table of contents|toc)\s*<\/h[1-6]>/i;
1340
2074
  return atxHeading.test(normalized) || setextHeading.test(normalized) || htmlHeading.test(normalized);
1341
2075
  }
2076
+ directoryContainsMarkdown(dirPath) {
2077
+ const entries = fs3.readdirSync(dirPath);
2078
+ for (const entry of entries) {
2079
+ const fullPath = `${dirPath}/${entry}`;
2080
+ const stat = fs3.statSync(fullPath);
2081
+ if (stat.isFile() && (entry.endsWith(".md") || entry.endsWith(".mdx"))) {
2082
+ return true;
2083
+ }
2084
+ }
2085
+ return false;
2086
+ }
2087
+ mergeTemplateOverrides(resolvedTemplatePath, sitePath, templateName) {
2088
+ if (this.options.templatePath) {
2089
+ return resolvedTemplatePath;
2090
+ }
2091
+ const overrideDir = path5.join(sitePath, "templates", templateName);
2092
+ const cacheDir = path5.join(sitePath, ".cache", "templates", templateName);
2093
+ if (!this.isPathWithinBasePath(overrideDir, sitePath) || !this.isPathWithinBasePath(cacheDir, sitePath)) {
2094
+ return resolvedTemplatePath;
2095
+ }
2096
+ if (!fs3.existsSync(overrideDir)) {
2097
+ return resolvedTemplatePath;
2098
+ }
2099
+ const overrideFiles = this.listFilesRecursive(overrideDir);
2100
+ if (fs3.existsSync(cacheDir) && this.isCacheFresh(overrideDir, cacheDir, overrideFiles)) {
2101
+ this._console.step("Using cached template overrides...");
2102
+ return cacheDir;
2103
+ }
2104
+ if (overrideFiles.length > 0) {
2105
+ this._console.step("Applying template overrides...");
2106
+ for (const file of overrideFiles) {
2107
+ this._console.info(`Template override: ${file}`);
2108
+ }
2109
+ }
2110
+ this.ensureCacheInGitignore(sitePath);
2111
+ if (fs3.existsSync(cacheDir)) {
2112
+ fs3.rmSync(cacheDir, { recursive: true, force: true });
2113
+ }
2114
+ fs3.mkdirSync(cacheDir, { recursive: true });
2115
+ this.copyDirectory(resolvedTemplatePath, cacheDir);
2116
+ this.copyDirectory(overrideDir, cacheDir);
2117
+ const manifestPath = path5.join(cacheDir, ".manifest.json");
2118
+ fs3.writeFileSync(manifestPath, JSON.stringify(overrideFiles));
2119
+ return cacheDir;
2120
+ }
2121
+ ensureCacheInGitignore(sitePath) {
2122
+ if (!this.options.autoUpdateIgnores) {
2123
+ return;
2124
+ }
2125
+ const cacheDir = path5.join(sitePath, ".cache");
2126
+ if (fs3.existsSync(cacheDir)) {
2127
+ return;
2128
+ }
2129
+ const gitignorePath = path5.join(sitePath, ".gitignore");
2130
+ const entry = ".cache";
2131
+ if (fs3.existsSync(gitignorePath)) {
2132
+ const content = fs3.readFileSync(gitignorePath, "utf8");
2133
+ if (!content.split("\n").some((line) => line.trim() === entry)) {
2134
+ fs3.appendFileSync(gitignorePath, `
2135
+ ${entry}
2136
+ `);
2137
+ this._console.info(`Added ${entry} to .gitignore`);
2138
+ }
2139
+ } else {
2140
+ fs3.writeFileSync(gitignorePath, `${entry}
2141
+ `);
2142
+ this._console.info("Created .gitignore with .cache");
2143
+ }
2144
+ }
2145
+ isCacheFresh(overrideDir, cacheDir, overrideFiles) {
2146
+ const manifestPath = path5.join(cacheDir, ".manifest.json");
2147
+ if (!fs3.existsSync(manifestPath)) {
2148
+ return false;
2149
+ }
2150
+ try {
2151
+ const previousFiles = JSON.parse(
2152
+ fs3.readFileSync(manifestPath, "utf8")
2153
+ );
2154
+ if (previousFiles.length !== overrideFiles.length || !previousFiles.every((f, i) => f === overrideFiles[i])) {
2155
+ return false;
2156
+ }
2157
+ } catch {
2158
+ return false;
2159
+ }
2160
+ for (const file of overrideFiles) {
2161
+ const overridePath = path5.join(overrideDir, file);
2162
+ const cachedPath = path5.join(cacheDir, file);
2163
+ if (!fs3.existsSync(cachedPath)) {
2164
+ return false;
2165
+ }
2166
+ const overrideMtime = fs3.statSync(overridePath).mtimeMs;
2167
+ const cachedMtime = fs3.statSync(cachedPath).mtimeMs;
2168
+ if (overrideMtime > cachedMtime) {
2169
+ return false;
2170
+ }
2171
+ }
2172
+ return true;
2173
+ }
2174
+ listFilesRecursive(dir, prefix = "") {
2175
+ const results = [];
2176
+ const entries = fs3.readdirSync(dir);
2177
+ for (const entry of entries) {
2178
+ if (entry.startsWith(".")) {
2179
+ continue;
2180
+ }
2181
+ const fullPath = path5.join(dir, entry);
2182
+ const relativePath = prefix ? `${prefix}/${entry}` : entry;
2183
+ const stat = fs3.lstatSync(fullPath);
2184
+ if (stat.isSymbolicLink()) {
2185
+ continue;
2186
+ }
2187
+ if (stat.isDirectory()) {
2188
+ results.push(...this.listFilesRecursive(fullPath, relativePath));
2189
+ } else {
2190
+ results.push(relativePath);
2191
+ }
2192
+ }
2193
+ return results;
2194
+ }
1342
2195
  copyDirectory(source, target) {
1343
- const files = fs2.readdirSync(source);
2196
+ const files = fs3.readdirSync(source);
1344
2197
  for (const file of files) {
1345
2198
  if (file.startsWith(".")) {
1346
2199
  continue;
1347
2200
  }
1348
2201
  const sourcePath = `${source}/${file}`;
1349
2202
  const targetPath = `${target}/${file}`;
1350
- const stat = fs2.lstatSync(sourcePath);
2203
+ const stat = fs3.lstatSync(sourcePath);
2204
+ if (stat.isSymbolicLink()) {
2205
+ continue;
2206
+ }
1351
2207
  if (stat.isDirectory()) {
1352
- fs2.mkdirSync(targetPath, { recursive: true });
2208
+ fs3.mkdirSync(targetPath, { recursive: true });
1353
2209
  this.copyDirectory(sourcePath, targetPath);
1354
2210
  } else {
1355
- fs2.mkdirSync(target, { recursive: true });
1356
- fs2.copyFileSync(sourcePath, targetPath);
2211
+ fs3.mkdirSync(target, { recursive: true });
2212
+ fs3.copyFileSync(sourcePath, targetPath);
1357
2213
  }
1358
2214
  }
1359
2215
  }
1360
- copyPublicFolder(sitePath, outputPath) {
2216
+ copyPublicFolder(sitePath, output) {
1361
2217
  const publicPath = `${sitePath}/public`;
1362
- if (!fs2.existsSync(publicPath)) {
2218
+ if (!fs3.existsSync(publicPath)) {
1363
2219
  return;
1364
2220
  }
1365
- this._console.log("Public folder found, copying contents to dist...");
1366
- const resolvedOutputPath = path4.resolve(outputPath);
1367
- this.copyPublicDirectory(
1368
- publicPath,
1369
- outputPath,
1370
- publicPath,
1371
- resolvedOutputPath
1372
- );
2221
+ this._console.step("Copying public folder...");
2222
+ const resolvedOutput = path5.resolve(output);
2223
+ this.copyPublicDirectory(publicPath, output, publicPath, resolvedOutput);
1373
2224
  }
1374
- copyPublicDirectory(source, target, basePath, outputPath) {
1375
- const files = fs2.readdirSync(source);
2225
+ copyPublicDirectory(source, target, basePath, output) {
2226
+ const files = fs3.readdirSync(source);
1376
2227
  for (const file of files) {
1377
2228
  const sourcePath = `${source}/${file}`;
1378
2229
  const targetPath = `${target}/${file}`;
1379
2230
  const relativePath = sourcePath.replace(`${basePath}/`, "");
1380
- const resolvedSourcePath = path4.resolve(sourcePath);
1381
- if (resolvedSourcePath === outputPath || resolvedSourcePath.startsWith(`${outputPath}${path4.sep}`)) {
2231
+ const resolvedSourcePath = path5.resolve(sourcePath);
2232
+ if (resolvedSourcePath === output || resolvedSourcePath.startsWith(`${output}${path5.sep}`)) {
2233
+ continue;
2234
+ }
2235
+ const stat = fs3.lstatSync(sourcePath);
2236
+ if (stat.isSymbolicLink()) {
1382
2237
  continue;
1383
2238
  }
1384
- const stat = fs2.lstatSync(sourcePath);
1385
2239
  if (stat.isDirectory()) {
1386
- fs2.mkdirSync(targetPath, { recursive: true });
1387
- this.copyPublicDirectory(sourcePath, targetPath, basePath, outputPath);
2240
+ fs3.mkdirSync(targetPath, { recursive: true });
2241
+ this.copyPublicDirectory(sourcePath, targetPath, basePath, output);
1388
2242
  } else {
1389
- fs2.mkdirSync(target, { recursive: true });
1390
- fs2.copyFileSync(sourcePath, targetPath);
1391
- this._console.log(` Copied: ${relativePath}`);
2243
+ fs3.mkdirSync(target, { recursive: true });
2244
+ fs3.copyFileSync(sourcePath, targetPath);
2245
+ this._console.fileCopied(relativePath);
2246
+ }
2247
+ }
2248
+ }
2249
+ copyDocumentSiblingAssets(data) {
2250
+ if (!data.documents) {
2251
+ return;
2252
+ }
2253
+ for (const document of data.documents) {
2254
+ const sourceDir = path5.dirname(document.documentPath);
2255
+ const outputDir = `${data.output}${path5.dirname(document.urlPath)}`;
2256
+ const availableAssets = this.listContentAssets(sourceDir);
2257
+ for (const assetRelPath of availableAssets) {
2258
+ if (document.markdown.includes(assetRelPath)) {
2259
+ const source = path5.join(sourceDir, assetRelPath);
2260
+ if (fs3.lstatSync(source).isSymbolicLink()) {
2261
+ continue;
2262
+ }
2263
+ const target = path5.join(outputDir, assetRelPath);
2264
+ fs3.mkdirSync(path5.dirname(target), { recursive: true });
2265
+ fs3.copyFileSync(source, target);
2266
+ }
2267
+ }
2268
+ }
2269
+ }
2270
+ listContentAssets(sourcePath, basePath) {
2271
+ const root = basePath ?? sourcePath;
2272
+ const results = [];
2273
+ if (!fs3.existsSync(sourcePath)) {
2274
+ return results;
2275
+ }
2276
+ const files = fs3.readdirSync(sourcePath);
2277
+ for (const file of files) {
2278
+ if (file.startsWith(".")) {
2279
+ continue;
2280
+ }
2281
+ const fullPath = `${sourcePath}/${file}`;
2282
+ const stat = fs3.lstatSync(fullPath);
2283
+ if (stat.isSymbolicLink()) {
2284
+ continue;
2285
+ }
2286
+ if (stat.isDirectory()) {
2287
+ results.push(...this.listContentAssets(fullPath, root));
2288
+ } else {
2289
+ const ext = path5.extname(file).toLowerCase();
2290
+ if (this.options.allowedAssets.includes(ext)) {
2291
+ results.push(path5.relative(root, fullPath));
2292
+ }
2293
+ }
2294
+ }
2295
+ return results;
2296
+ }
2297
+ copyContentAssets(sourcePath, targetPath) {
2298
+ if (!fs3.existsSync(sourcePath)) {
2299
+ return;
2300
+ }
2301
+ const files = fs3.readdirSync(sourcePath);
2302
+ for (const file of files) {
2303
+ if (file.startsWith(".")) {
2304
+ continue;
2305
+ }
2306
+ const source = `${sourcePath}/${file}`;
2307
+ const target = `${targetPath}/${file}`;
2308
+ const stat = fs3.lstatSync(source);
2309
+ if (stat.isSymbolicLink()) {
2310
+ continue;
2311
+ }
2312
+ if (stat.isDirectory()) {
2313
+ this.copyContentAssets(source, target);
2314
+ } else {
2315
+ const ext = path5.extname(file).toLowerCase();
2316
+ if (this.options.allowedAssets.includes(ext)) {
2317
+ fs3.mkdirSync(targetPath, { recursive: true });
2318
+ fs3.copyFileSync(source, target);
2319
+ }
1392
2320
  }
1393
2321
  }
1394
2322
  }
1395
2323
  };
1396
2324
 
1397
2325
  // src/init.ts
1398
- var doculaconfigmjs = "ZXhwb3J0IGNvbnN0IG9wdGlvbnMgPSB7Cgl0ZW1wbGF0ZTogJ21vZGVybicsCglvdXRwdXRQYXRoOiAnLi9kaXN0JywKCXNpdGVQYXRoOiAnLi9zaXRlJywKCWdpdGh1YlBhdGg6ICdqYXJlZHdyYXkvZG9jdWxhJywKCXNpdGVUaXRsZTogJ0RvY3VsYScsCglzaXRlRGVzY3JpcHRpb246ICdCZWF1dGlmdWwgV2Vic2l0ZSBmb3IgWW91ciBQcm9qZWN0cycsCglzaXRlVXJsOiAnaHR0cHM6Ly9kb2N1bGEub3JnJywKfTsKCg==";
1399
- var doculaconfigts = "aW1wb3J0IHR5cGUgeyBEb2N1bGFPcHRpb25zIH0gZnJvbSAnZG9jdWxhJzsKCmV4cG9ydCBjb25zdCBvcHRpb25zOiBQYXJ0aWFsPERvY3VsYU9wdGlvbnM+ID0gewoJdGVtcGxhdGU6ICdtb2Rlcm4nLAoJb3V0cHV0UGF0aDogJy4vZGlzdCcsCglzaXRlUGF0aDogJy4vc2l0ZScsCglnaXRodWJQYXRoOiAnamFyZWR3cmF5L2RvY3VsYScsCglzaXRlVGl0bGU6ICdEb2N1bGEnLAoJc2l0ZURlc2NyaXB0aW9uOiAnQmVhdXRpZnVsIFdlYnNpdGUgZm9yIFlvdXIgUHJvamVjdHMnLAoJc2l0ZVVybDogJ2h0dHBzOi8vZG9jdWxhLm9yZycsCn07Cg==";
2326
+ var doculaconfigmjs = "ZXhwb3J0IGNvbnN0IG9wdGlvbnMgPSB7CglvdXRwdXQ6ICcuL2Rpc3QnLAoJc2l0ZVBhdGg6ICcuL3NpdGUnLAoJZ2l0aHViUGF0aDogJ2phcmVkd3JheS9kb2N1bGEnLAoJc2l0ZVRpdGxlOiAnRG9jdWxhJywKCXNpdGVEZXNjcmlwdGlvbjogJ0JlYXV0aWZ1bCBXZWJzaXRlIGZvciBZb3VyIFByb2plY3RzJywKCXNpdGVVcmw6ICdodHRwczovL2RvY3VsYS5vcmcnLAoJLy8gb3BlbkFwaVVybDogJy9hcGkvc3dhZ2dlci5qc29uJywKCS8vIGVuYWJsZVJlbGVhc2VDaGFuZ2Vsb2c6IHRydWUsCgkvLyBlbmFibGVMbG1zVHh0OiB0cnVlLAoJLy8gaG9tZVBhZ2U6IHRydWUsCgkvLyB0aGVtZU1vZGU6ICdsaWdodCcsCn07Cg==";
2327
+ var doculaconfigts = "aW1wb3J0IHR5cGUgeyBEb2N1bGFPcHRpb25zIH0gZnJvbSAnZG9jdWxhJzsKCmV4cG9ydCBjb25zdCBvcHRpb25zOiBQYXJ0aWFsPERvY3VsYU9wdGlvbnM+ID0gewoJb3V0cHV0OiAnLi9kaXN0JywKCXNpdGVQYXRoOiAnLi9zaXRlJywKCWdpdGh1YlBhdGg6ICdqYXJlZHdyYXkvZG9jdWxhJywKCXNpdGVUaXRsZTogJ0RvY3VsYScsCglzaXRlRGVzY3JpcHRpb246ICdCZWF1dGlmdWwgV2Vic2l0ZSBmb3IgWW91ciBQcm9qZWN0cycsCglzaXRlVXJsOiAnaHR0cHM6Ly9kb2N1bGEub3JnJywKCS8vIG9wZW5BcGlVcmw6ICcvYXBpL3N3YWdnZXIuanNvbicsCgkvLyBlbmFibGVSZWxlYXNlQ2hhbmdlbG9nOiB0cnVlLAoJLy8gZW5hYmxlTGxtc1R4dDogdHJ1ZSwKCS8vIGhvbWVQYWdlOiB0cnVlLAoJLy8gdGhlbWVNb2RlOiAnbGlnaHQnLAp9Owo=";
1400
2328
  var faviconico = "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";
1401
2329
  var logopng = "iVBORw0KGgoAAAANSUhEUgAAAxEAAAHXCAYAAADDSdfmAAAACXBIWXMAACxLAAAsSwGlPZapAAAFw2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDMgNzkuOTY5MGE4N2ZjLCAyMDI1LzAzLzA2LTIwOjUwOjE2ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjYuNiAoTWFjaW50b3NoKSIgeG1wOkNyZWF0ZURhdGU9IjIwMjUtMDUtMThUMTM6MzM6NTgtMDc6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDI1LTA1LTE4VDEzOjM2OjE5LTA3OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDI1LTA1LTE4VDEzOjM2OjE5LTA3OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo5YTc4ZDY2Ny02NjAzLTQwNmUtYTViZC02MjYzMTNjY2VmY2QiIHhtcE1NOkRvY3VtZW50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";
1402
- var variablescss = "OnJvb3QgewogICAgLS1mb250LWZhbWlseTogJ09wZW4gU2FucycsIHNhbnMtc2VyaWY7CiAgCiAgICAtLWNvbG9yLXByaW1hcnk6ICAjMzIyZDNjOwogICAgLS1jb2xvci1zZWNvbmRhcnk6ICM4Y2RjMDA7CiAgICAtLWNvbG9yLXNlY29uZGFyeS1kYXJrOiAjOGNkYzAwOwogICAgLS1jb2xvci10ZXh0OiAjMzIyZDNjOwogIAogICAgLS1iYWNrZ3JvdW5kOiAjZmZmZmZmOwogICAgLS1ob21lLWJhY2tncm91bmQ6ICNmZmZmZmY7CiAgICAtLWhlYWRlci1iYWNrZ3JvdW5kOiAjZmZmZmZmOwogIAogICAgLS1zaWRlYmFyLWJhY2tncm91bmQ6ICNmZmZmZmY7CiAgICAtLXNpZGViYXItdGV4dDogIzMyMmQzYzsKICAgIC0tc2lkZWJhci10ZXh0LWFjdGl2ZTogIzdkNzg4NzsKICAKICAgIC0tYm9yZGVyOiByZ2JhKDIzOCwyMzgsMjQ1LDEpOwogIAogICAgLS1iYWNrZ3JvdW5kLXNlYXJjaC1oaWdobGlnaHQ6IHZhcigtLWNvbG9yLXNlY29uZGFyeS1kYXJrKTsKICAgIC0tY29sb3Itc2VhcmNoLWhpZ2hsaWdodDogI2ZmZmZmZjsKICAgIC0tc2VhcmNoLWlucHV0LWJhY2tncm91bmQ6IHZhcigtLWhlYWRlci1iYWNrZ3JvdW5kKTsKICAKICAgIC0tY29kZTogcmdiYSgyMzgsMjM4LDI0NSwxKTsKICAKICAgIC0tcGFnZWZpbmQtdWktdGV4dDogdmFyKC0tY29sb3ItdGV4dCkgIWltcG9ydGFudDsKICAgIC0tcGFnZWZpbmQtdWktZm9udDogdmFyKC0tZm9udC1mYW1pbHkpICFpbXBvcnRhbnQ7CiAgICAtLXBhZ2VmaW5kLXVpLWJhY2tncm91bmQ6IHZhcigtLWJhY2tncm91bmQpICFpbXBvcnRhbnQ7CiAgICAtLXBhZ2VmaW5kLXVpLWJvcmRlcjogdmFyKC0tYm9yZGVyKSAhaW1wb3J0YW50OwogICAgLS1wYWdlZmluZC11aS1zY2FsZTogLjkgIWltcG9ydGFudDsKICB9";
1403
2330
 
1404
2331
  // src/docula.ts
1405
2332
  import { Writr as Writr2 } from "writr";
@@ -1409,6 +2336,7 @@ var Docula = class {
1409
2336
  // biome-ignore lint/suspicious/noExplicitAny: need to fix
1410
2337
  _configFileModule = {};
1411
2338
  _server;
2339
+ _watcher;
1412
2340
  /**
1413
2341
  * Initialize the Docula class
1414
2342
  * @param {DoculaOptions} options
@@ -1441,6 +2369,13 @@ var Docula = class {
1441
2369
  get server() {
1442
2370
  return this._server;
1443
2371
  }
2372
+ /**
2373
+ * The file watcher used in watch mode
2374
+ * @returns {fs.FSWatcher | undefined}
2375
+ */
2376
+ get watcher() {
2377
+ return this._watcher;
2378
+ }
1444
2379
  /**
1445
2380
  * The config file module. This is the module that is loaded from the docula.config.ts or docula.config.mjs file
1446
2381
  * @returns {any}
@@ -1449,14 +2384,30 @@ var Docula = class {
1449
2384
  get configFileModule() {
1450
2385
  return this._configFileModule;
1451
2386
  }
2387
+ /**
2388
+ * Remove the .cache directory inside the site path.
2389
+ * Resolves the path and verifies it stays within sitePath to prevent
2390
+ * path-traversal attacks.
2391
+ * @param {string} sitePath
2392
+ */
2393
+ cleanCache(sitePath) {
2394
+ const resolvedSitePath = path6.resolve(sitePath);
2395
+ const cachePath = path6.resolve(resolvedSitePath, ".cache");
2396
+ if (!cachePath.startsWith(resolvedSitePath + path6.sep) && cachePath !== resolvedSitePath) {
2397
+ return;
2398
+ }
2399
+ if (fs4.existsSync(cachePath)) {
2400
+ fs4.rmSync(cachePath, { recursive: true, force: true });
2401
+ }
2402
+ }
1452
2403
  /**
1453
2404
  * Check for updates
1454
2405
  * @returns {void}
1455
2406
  */
1456
2407
  checkForUpdates() {
1457
- const packageJsonPath = path5.join(process4.cwd(), "package.json");
1458
- if (fs3.existsSync(packageJsonPath)) {
1459
- const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf8"));
2408
+ const packageJsonPath = path6.join(process4.cwd(), "package.json");
2409
+ if (fs4.existsSync(packageJsonPath)) {
2410
+ const packageJson = JSON.parse(fs4.readFileSync(packageJsonPath, "utf8"));
1460
2411
  updateNotifier({ pkg: packageJson }).notify();
1461
2412
  }
1462
2413
  }
@@ -1468,7 +2419,6 @@ var Docula = class {
1468
2419
  async execute(process5) {
1469
2420
  this.checkForUpdates();
1470
2421
  const consoleProcess = this._console.parseProcessArgv(process5.argv);
1471
- this.options.singlePage = this.isSinglePageWebsite(this.options.sitePath);
1472
2422
  if (consoleProcess.args.sitePath) {
1473
2423
  this.options.sitePath = consoleProcess.args.sitePath;
1474
2424
  }
@@ -1490,7 +2440,10 @@ var Docula = class {
1490
2440
  this.options.template = consoleProcess.args.template;
1491
2441
  }
1492
2442
  if (consoleProcess.args.output) {
1493
- this.options.outputPath = consoleProcess.args.output;
2443
+ this.options.output = consoleProcess.args.output;
2444
+ }
2445
+ if (consoleProcess.args.port !== void 0 && !Number.isNaN(consoleProcess.args.port)) {
2446
+ this.options.port = consoleProcess.args.port;
1494
2447
  }
1495
2448
  switch (consoleProcess.command) {
1496
2449
  case "init": {
@@ -1509,31 +2462,35 @@ var Docula = class {
1509
2462
  break;
1510
2463
  }
1511
2464
  case "serve": {
1512
- const builder = new DoculaBuilder(this.options);
1513
- await builder.build();
2465
+ if (consoleProcess.args.build || consoleProcess.args.watch) {
2466
+ if (consoleProcess.args.clean && fs4.existsSync(this.options.output)) {
2467
+ fs4.rmSync(this.options.output, { recursive: true, force: true });
2468
+ }
2469
+ if (consoleProcess.args.clean) {
2470
+ this.cleanCache(this.options.sitePath);
2471
+ }
2472
+ const builder = new DoculaBuilder(this.options);
2473
+ await builder.build();
2474
+ if (consoleProcess.args.watch) {
2475
+ this.watch(this.options, builder);
2476
+ }
2477
+ }
1514
2478
  await this.serve(this.options);
1515
2479
  break;
1516
2480
  }
1517
2481
  default: {
2482
+ if (consoleProcess.args.clean && fs4.existsSync(this.options.output)) {
2483
+ fs4.rmSync(this.options.output, { recursive: true, force: true });
2484
+ }
2485
+ if (consoleProcess.args.clean) {
2486
+ this.cleanCache(this.options.sitePath);
2487
+ }
1518
2488
  const builder = new DoculaBuilder(this.options);
1519
2489
  await builder.build();
1520
2490
  break;
1521
2491
  }
1522
2492
  }
1523
2493
  }
1524
- /**
1525
- * Checks if the site is a single page website
1526
- * @param {string} sitePath
1527
- * @returns {boolean}
1528
- */
1529
- isSinglePageWebsite(sitePath) {
1530
- const documentationPath = `${sitePath}/docs`;
1531
- if (!fs3.existsSync(documentationPath)) {
1532
- return true;
1533
- }
1534
- const files = fs3.readdirSync(documentationPath);
1535
- return files.length === 0;
1536
- }
1537
2494
  /**
1538
2495
  * Generate the init files
1539
2496
  * @param {string} sitePath
@@ -1541,8 +2498,8 @@ var Docula = class {
1541
2498
  * @returns {void}
1542
2499
  */
1543
2500
  generateInit(sitePath, typescript = false) {
1544
- if (!fs3.existsSync(sitePath)) {
1545
- fs3.mkdirSync(sitePath);
2501
+ if (!fs4.existsSync(sitePath)) {
2502
+ fs4.mkdirSync(sitePath);
1546
2503
  }
1547
2504
  const configExtension = typescript ? "ts" : "mjs";
1548
2505
  const doculaConfigFile = `${sitePath}/docula.config.${configExtension}`;
@@ -1550,15 +2507,13 @@ var Docula = class {
1550
2507
  typescript ? doculaconfigts : doculaconfigmjs,
1551
2508
  "base64"
1552
2509
  );
1553
- fs3.writeFileSync(doculaConfigFile, doculaConfigFileBuffer);
2510
+ fs4.writeFileSync(doculaConfigFile, doculaConfigFileBuffer);
1554
2511
  const logoBuffer = Buffer.from(logopng, "base64");
1555
- fs3.writeFileSync(`${sitePath}/logo.png`, logoBuffer);
2512
+ fs4.writeFileSync(`${sitePath}/logo.png`, logoBuffer);
1556
2513
  const faviconBuffer = Buffer.from(faviconico, "base64");
1557
- fs3.writeFileSync(`${sitePath}/favicon.ico`, faviconBuffer);
1558
- const variablesBuffer = Buffer.from(variablescss, "base64");
1559
- fs3.writeFileSync(`${sitePath}/variables.css`, variablesBuffer);
2514
+ fs4.writeFileSync(`${sitePath}/favicon.ico`, faviconBuffer);
1560
2515
  this._console.log(
1561
- `docula initialized. Please update the ${doculaConfigFile} file with your site information. In addition, you can replace the image, favicon, and style the site with site.css file.`
2516
+ `docula initialized. Please update the ${doculaConfigFile} file with your site information. In addition, you can replace the image and favicon.`
1562
2517
  );
1563
2518
  }
1564
2519
  /**
@@ -1566,7 +2521,7 @@ var Docula = class {
1566
2521
  * @returns {string}
1567
2522
  */
1568
2523
  getVersion() {
1569
- const packageJson = fs3.readFileSync("./package.json", "utf8");
2524
+ const packageJson = fs4.readFileSync("./package.json", "utf8");
1570
2525
  const packageObject = JSON.parse(packageJson);
1571
2526
  return packageObject.version;
1572
2527
  }
@@ -1577,24 +2532,76 @@ var Docula = class {
1577
2532
  * @returns {Promise<void>}
1578
2533
  */
1579
2534
  async loadConfigFile(sitePath) {
1580
- if (!fs3.existsSync(sitePath)) {
2535
+ if (!fs4.existsSync(sitePath)) {
1581
2536
  return;
1582
2537
  }
1583
2538
  const tsConfigFile = `${sitePath}/docula.config.ts`;
1584
2539
  const mjsConfigFile = `${sitePath}/docula.config.mjs`;
1585
- if (fs3.existsSync(tsConfigFile)) {
1586
- const absolutePath = path5.resolve(tsConfigFile);
2540
+ if (fs4.existsSync(tsConfigFile)) {
2541
+ const absolutePath = path6.resolve(tsConfigFile);
1587
2542
  const jiti = createJiti(import.meta.url, {
1588
2543
  interopDefault: true
1589
2544
  });
1590
2545
  this._configFileModule = await jiti.import(absolutePath);
1591
2546
  return;
1592
2547
  }
1593
- if (fs3.existsSync(mjsConfigFile)) {
1594
- const absolutePath = path5.resolve(mjsConfigFile);
2548
+ if (fs4.existsSync(mjsConfigFile)) {
2549
+ const absolutePath = path6.resolve(mjsConfigFile);
1595
2550
  this._configFileModule = await import(pathToFileURL(absolutePath).href);
1596
2551
  }
1597
2552
  }
2553
+ /**
2554
+ * Watch the site path for file changes and rebuild on change
2555
+ * @param {DoculaOptions} options
2556
+ * @param {DoculaBuilder} builder
2557
+ * @returns {fs.FSWatcher}
2558
+ */
2559
+ watch(options, builder) {
2560
+ if (this._watcher) {
2561
+ this._watcher.close();
2562
+ }
2563
+ let debounceTimer;
2564
+ let isBuilding = false;
2565
+ let pendingRebuild = false;
2566
+ const outputRelative = path6.relative(options.sitePath, options.output);
2567
+ const runBuild = async (filename) => {
2568
+ isBuilding = true;
2569
+ this._console.info(`File changed: ${filename}, rebuilding...`);
2570
+ try {
2571
+ await builder.build();
2572
+ this._console.success("Rebuild complete");
2573
+ } catch (error) {
2574
+ this._console.error(`Rebuild failed: ${error.message}`);
2575
+ } finally {
2576
+ isBuilding = false;
2577
+ if (pendingRebuild) {
2578
+ pendingRebuild = false;
2579
+ await runBuild("queued changes");
2580
+ }
2581
+ }
2582
+ };
2583
+ this._watcher = fs4.watch(
2584
+ options.sitePath,
2585
+ { recursive: true },
2586
+ (_eventType, filename) => {
2587
+ if (filename && outputRelative && !outputRelative.startsWith("..") && (String(filename) === outputRelative || String(filename).startsWith(`${outputRelative}${path6.sep}`))) {
2588
+ return;
2589
+ }
2590
+ if (isBuilding) {
2591
+ pendingRebuild = true;
2592
+ return;
2593
+ }
2594
+ if (debounceTimer) {
2595
+ clearTimeout(debounceTimer);
2596
+ }
2597
+ debounceTimer = setTimeout(async () => {
2598
+ await runBuild(String(filename));
2599
+ }, 300);
2600
+ }
2601
+ );
2602
+ this._console.info("Watching for file changes...");
2603
+ return this._watcher;
2604
+ }
1598
2605
  /**
1599
2606
  * Serve the site based on the options (port and output path)
1600
2607
  * @param {DoculaOptions} options
@@ -1605,24 +2612,45 @@ var Docula = class {
1605
2612
  this._server.close();
1606
2613
  }
1607
2614
  const { port } = options;
1608
- const { outputPath } = options;
2615
+ const { output } = options;
2616
+ if (!fs4.existsSync(output)) {
2617
+ fs4.mkdirSync(output, { recursive: true });
2618
+ }
1609
2619
  const config = {
1610
- public: outputPath
2620
+ public: output
1611
2621
  };
1612
- this._server = http.createServer(
1613
- async (request, response) => (
1614
- /* v8 ignore next -- @preserve */
1615
- handler(request, response, config)
1616
- )
1617
- );
2622
+ this._server = http.createServer(async (request, response) => {
2623
+ const start = Date.now();
2624
+ response.on("finish", () => {
2625
+ const duration = Date.now() - start;
2626
+ this._console.serverLog(
2627
+ request.method ?? "GET",
2628
+ request.url ?? "/",
2629
+ response.statusCode,
2630
+ duration
2631
+ );
2632
+ });
2633
+ handler(request, response, config);
2634
+ });
1618
2635
  this._server.listen(port, () => {
1619
- this._console.log(`Docula \u{1F987} at http://localhost:${port}`);
2636
+ this._console.banner(
2637
+ `
2638
+ Docula \u{1F987} at http://localhost:${port}
2639
+ `
2640
+ );
1620
2641
  });
1621
2642
  return this._server;
1622
2643
  }
1623
2644
  };
1624
2645
  export {
2646
+ DoculaOptions,
1625
2647
  Writr2 as Writr,
1626
2648
  Docula as default
1627
2649
  };
1628
2650
  /* v8 ignore next -- @preserve */
2651
+ /* v8 ignore next 3 -- @preserve */
2652
+ /* v8 ignore next 2 -- @preserve */
2653
+ /* v8 ignore next 9 -- @preserve */
2654
+ /* v8 ignore next 4 -- @preserve */
2655
+ /* v8 ignore start -- @preserve */
2656
+ /* v8 ignore stop -- @preserve */