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/README.md +119 -5
- package/dist/docula.d.ts +254 -15
- package/dist/docula.js +1255 -227
- package/package.json +10 -12
- package/templates/classic/api.hbs +203 -17
- package/templates/classic/changelog-entry.hbs +2 -0
- package/templates/classic/changelog.hbs +2 -0
- package/templates/classic/css/api.css +753 -0
- package/templates/classic/css/base.css +29 -0
- package/templates/classic/docs.hbs +2 -0
- package/templates/classic/home.hbs +2 -0
- package/templates/classic/includes/api-try-it.hbs +61 -0
- package/templates/classic/js/api.js +282 -0
- package/templates/modern/api.hbs +203 -18
- package/templates/modern/css/api.css +1051 -0
- package/templates/modern/css/home.css +37 -0
- package/templates/modern/css/styles.css +65 -6
- package/templates/modern/home.hbs +13 -0
- package/templates/modern/includes/api-try-it.hbs +61 -0
- package/templates/modern/includes/header-bar.hbs +23 -3
- package/templates/modern/includes/header.hbs +2 -2
- package/templates/modern/includes/scripts.hbs +75 -17
- package/templates/modern/includes/theme-toggle.hbs +2 -2
- package/templates/modern/js/api.js +300 -0
package/dist/docula.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/docula.ts
|
|
2
|
-
import
|
|
2
|
+
import fs4 from "fs";
|
|
3
3
|
import http from "http";
|
|
4
|
-
import
|
|
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
|
|
13
|
-
import
|
|
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(
|
|
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("
|
|
34
|
-
console.log("
|
|
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
|
-
"
|
|
539
|
+
` ${green("serve")} Serve the project as a local website`
|
|
37
540
|
);
|
|
38
|
-
console.log("
|
|
39
|
-
console.log("
|
|
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("
|
|
544
|
+
console.log(bold(cyan(" Arguments init:")));
|
|
43
545
|
console.log(
|
|
44
|
-
"
|
|
546
|
+
` ${yellow("--typescript")} Generate TypeScript config file (docula.config.ts)`
|
|
45
547
|
);
|
|
46
548
|
console.log(
|
|
47
|
-
"
|
|
549
|
+
` ${yellow("-s, --site")} Set the path where site files are located`
|
|
48
550
|
);
|
|
49
551
|
console.log();
|
|
50
|
-
console.log("
|
|
51
|
-
console.log(
|
|
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
|
-
"
|
|
557
|
+
` ${yellow("-c, --clean")} Clean the output directory before building`
|
|
54
558
|
);
|
|
55
559
|
console.log(
|
|
56
|
-
"
|
|
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
|
-
"
|
|
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("
|
|
64
|
-
console.log(
|
|
65
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
854
|
+
output = path3.join(process3.cwd(), "./dist");
|
|
273
855
|
/**
|
|
274
856
|
* Path to the site directory
|
|
275
857
|
*/
|
|
276
|
-
sitePath =
|
|
858
|
+
sitePath = path3.join(process3.cwd(), "./site");
|
|
277
859
|
/**
|
|
278
860
|
* Path to the github repository
|
|
279
861
|
*/
|
|
280
|
-
githubPath = "
|
|
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 =
|
|
975
|
+
this.templatePath = path3.join(process3.cwd(), this.templatePath);
|
|
338
976
|
}
|
|
339
|
-
if (options.
|
|
340
|
-
this.
|
|
341
|
-
this.
|
|
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 =
|
|
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
|
|
385
|
-
import
|
|
1034
|
+
import fs2 from "fs";
|
|
1035
|
+
import path4 from "path";
|
|
386
1036
|
function getBuiltInTemplatesDir() {
|
|
387
|
-
return
|
|
1037
|
+
return path4.join(import.meta.url, "../../templates").replace("file:", "");
|
|
388
1038
|
}
|
|
389
1039
|
function listBuiltInTemplates() {
|
|
390
1040
|
const templatesDir = getBuiltInTemplatesDir();
|
|
391
|
-
if (!
|
|
1041
|
+
if (!fs2.existsSync(templatesDir)) {
|
|
392
1042
|
return [];
|
|
393
1043
|
}
|
|
394
|
-
return
|
|
395
|
-
(entry) =>
|
|
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 =
|
|
403
|
-
if (!
|
|
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 =
|
|
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
|
-
|
|
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 &&
|
|
1099
|
+
if (!doculaData.openApiUrl && fs3.existsSync(`${doculaData.sitePath}/api/swagger.json`)) {
|
|
447
1100
|
doculaData.openApiUrl = "/api/swagger.json";
|
|
448
1101
|
}
|
|
449
|
-
|
|
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
|
-
|
|
513
|
-
|
|
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.
|
|
1191
|
+
`${this.options.output}/favicon.ico`
|
|
516
1192
|
);
|
|
1193
|
+
this._console.fileCopied("favicon.ico");
|
|
517
1194
|
}
|
|
518
|
-
if (
|
|
519
|
-
await
|
|
1195
|
+
if (fs3.existsSync(`${siteRelativePath}/logo.svg`)) {
|
|
1196
|
+
await fs3.promises.copyFile(
|
|
520
1197
|
`${siteRelativePath}/logo.svg`,
|
|
521
|
-
`${this.options.
|
|
1198
|
+
`${this.options.output}/logo.svg`
|
|
522
1199
|
);
|
|
1200
|
+
this._console.fileCopied("logo.svg");
|
|
523
1201
|
}
|
|
524
|
-
if (
|
|
525
|
-
await
|
|
1202
|
+
if (fs3.existsSync(`${siteRelativePath}/logo_horizontal.png`)) {
|
|
1203
|
+
await fs3.promises.copyFile(
|
|
526
1204
|
`${siteRelativePath}/logo_horizontal.png`,
|
|
527
|
-
`${this.options.
|
|
1205
|
+
`${this.options.output}/logo_horizontal.png`
|
|
528
1206
|
);
|
|
1207
|
+
this._console.fileCopied("logo_horizontal.png");
|
|
529
1208
|
}
|
|
530
|
-
if (
|
|
1209
|
+
if (fs3.existsSync(`${resolvedTemplatePath}/css`)) {
|
|
531
1210
|
this.copyDirectory(
|
|
532
1211
|
`${resolvedTemplatePath}/css`,
|
|
533
|
-
`${this.options.
|
|
1212
|
+
`${this.options.output}/css`
|
|
534
1213
|
);
|
|
1214
|
+
this._console.fileCopied("css/");
|
|
535
1215
|
}
|
|
536
|
-
if (
|
|
537
|
-
|
|
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.
|
|
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.
|
|
1244
|
+
this._console.success(`Build completed in ${executionTime}ms`);
|
|
547
1245
|
}
|
|
548
1246
|
validateOptions(options) {
|
|
549
|
-
if (options.githubPath
|
|
550
|
-
throw new Error("
|
|
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
|
-
|
|
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 (
|
|
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(
|
|
1306
|
+
async getTemplateFile(path7, name) {
|
|
602
1307
|
let result;
|
|
603
|
-
const files = await
|
|
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 {
|
|
616
|
-
const robotsPath = `${
|
|
617
|
-
await
|
|
618
|
-
await (
|
|
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.
|
|
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
|
|
650
|
-
await
|
|
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
|
|
657
|
-
const llmsOutputPath = `${data.
|
|
658
|
-
const llmsFullOutputPath = `${data.
|
|
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
|
|
1373
|
+
await fs3.promises.writeFile(llmsOutputPath, llmsOverrideContent, "utf8");
|
|
669
1374
|
} else {
|
|
670
1375
|
const llmsContent = this.generateLlmsIndexContent(data);
|
|
671
|
-
await
|
|
1376
|
+
await fs3.promises.writeFile(llmsOutputPath, llmsContent, "utf8");
|
|
672
1377
|
}
|
|
1378
|
+
this._console.fileBuilt("llms.txt");
|
|
673
1379
|
if (llmsFullOverrideContent !== void 0) {
|
|
674
|
-
await
|
|
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
|
|
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
|
|
1556
|
+
return path5.join(data.sitePath, normalizedPath);
|
|
850
1557
|
}
|
|
851
1558
|
async getSafeSiteOverrideFileContent(sitePath, fileName) {
|
|
852
|
-
const resolvedSitePath =
|
|
853
|
-
const candidatePath =
|
|
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
|
|
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
|
|
870
|
-
realCandidatePath = await
|
|
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
|
|
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 =
|
|
885
|
-
const resolvedLocalOpenApiPath =
|
|
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
|
|
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
|
|
902
|
-
realLocalOpenApiPath = await
|
|
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
|
|
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 =
|
|
919
|
-
|
|
920
|
-
|
|
1625
|
+
const relativePath = path5.relative(
|
|
1626
|
+
path5.resolve(basePath),
|
|
1627
|
+
path5.resolve(candidatePath)
|
|
921
1628
|
);
|
|
922
|
-
return relativePath === "" || !relativePath.startsWith("..") && !
|
|
1629
|
+
return relativePath === "" || !relativePath.startsWith("..") && !path5.isAbsolute(relativePath);
|
|
923
1630
|
}
|
|
924
1631
|
toPosixPath(filePath) {
|
|
925
|
-
return filePath.replaceAll(
|
|
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.
|
|
930
|
-
await
|
|
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
|
|
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.
|
|
955
|
-
await
|
|
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
|
|
1673
|
+
await fs3.promises.writeFile(indexPath, documentContent, "utf8");
|
|
967
1674
|
}
|
|
968
1675
|
async buildReadmeSection(data) {
|
|
969
1676
|
let htmlReadme = "";
|
|
970
|
-
if (
|
|
971
|
-
const readmeContent =
|
|
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 (
|
|
982
|
-
const announcementContent =
|
|
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
|
|
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
|
|
1701
|
+
await fs3.promises.mkdir(`${data.output}/${folder}`, {
|
|
995
1702
|
recursive: true
|
|
996
1703
|
});
|
|
997
|
-
const slug = `${data.
|
|
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
|
|
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.
|
|
1015
|
-
const apiOutputPath = `${data.
|
|
1016
|
-
await
|
|
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 (
|
|
1019
|
-
await
|
|
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
|
|
1760
|
+
await fs3.promises.writeFile(apiPath, apiContent, "utf8");
|
|
1031
1761
|
}
|
|
1032
1762
|
getChangelogEntries(changelogPath) {
|
|
1033
1763
|
const entries = [];
|
|
1034
|
-
if (!
|
|
1764
|
+
if (!fs3.existsSync(changelogPath)) {
|
|
1035
1765
|
return entries;
|
|
1036
1766
|
}
|
|
1037
|
-
const files =
|
|
1767
|
+
const files = fs3.readdirSync(changelogPath);
|
|
1038
1768
|
for (const file of files) {
|
|
1039
1769
|
const filePath = `${changelogPath}/${file}`;
|
|
1040
|
-
const stats =
|
|
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 =
|
|
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 =
|
|
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.
|
|
1877
|
+
const changelogOutputPath = `${data.output}/changelog`;
|
|
1148
1878
|
const changelogIndexPath = `${changelogOutputPath}/index.html`;
|
|
1149
|
-
await
|
|
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
|
|
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.
|
|
1165
|
-
await
|
|
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
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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 (
|
|
1260
|
-
const documentList =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
2203
|
+
const stat = fs3.lstatSync(sourcePath);
|
|
2204
|
+
if (stat.isSymbolicLink()) {
|
|
2205
|
+
continue;
|
|
2206
|
+
}
|
|
1351
2207
|
if (stat.isDirectory()) {
|
|
1352
|
-
|
|
2208
|
+
fs3.mkdirSync(targetPath, { recursive: true });
|
|
1353
2209
|
this.copyDirectory(sourcePath, targetPath);
|
|
1354
2210
|
} else {
|
|
1355
|
-
|
|
1356
|
-
|
|
2211
|
+
fs3.mkdirSync(target, { recursive: true });
|
|
2212
|
+
fs3.copyFileSync(sourcePath, targetPath);
|
|
1357
2213
|
}
|
|
1358
2214
|
}
|
|
1359
2215
|
}
|
|
1360
|
-
copyPublicFolder(sitePath,
|
|
2216
|
+
copyPublicFolder(sitePath, output) {
|
|
1361
2217
|
const publicPath = `${sitePath}/public`;
|
|
1362
|
-
if (!
|
|
2218
|
+
if (!fs3.existsSync(publicPath)) {
|
|
1363
2219
|
return;
|
|
1364
2220
|
}
|
|
1365
|
-
this._console.
|
|
1366
|
-
const
|
|
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,
|
|
1375
|
-
const files =
|
|
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 =
|
|
1381
|
-
if (resolvedSourcePath ===
|
|
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
|
-
|
|
1387
|
-
this.copyPublicDirectory(sourcePath, targetPath, basePath,
|
|
2240
|
+
fs3.mkdirSync(targetPath, { recursive: true });
|
|
2241
|
+
this.copyPublicDirectory(sourcePath, targetPath, basePath, output);
|
|
1388
2242
|
} else {
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
this._console.
|
|
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 = "
|
|
1399
|
-
var doculaconfigts = "aW1wb3J0IHR5cGUgeyBEb2N1bGFPcHRpb25zIH0gZnJvbSAnZG9jdWxhJzsKCmV4cG9ydCBjb25zdCBvcHRpb25zOiBQYXJ0aWFsPERvY3VsYU9wdGlvbnM+
|
|
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 =
|
|
1458
|
-
if (
|
|
1459
|
-
const packageJson = JSON.parse(
|
|
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.
|
|
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
|
-
|
|
1513
|
-
|
|
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 (!
|
|
1545
|
-
|
|
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
|
-
|
|
2510
|
+
fs4.writeFileSync(doculaConfigFile, doculaConfigFileBuffer);
|
|
1554
2511
|
const logoBuffer = Buffer.from(logopng, "base64");
|
|
1555
|
-
|
|
2512
|
+
fs4.writeFileSync(`${sitePath}/logo.png`, logoBuffer);
|
|
1556
2513
|
const faviconBuffer = Buffer.from(faviconico, "base64");
|
|
1557
|
-
|
|
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
|
|
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 =
|
|
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 (!
|
|
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 (
|
|
1586
|
-
const absolutePath =
|
|
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 (
|
|
1594
|
-
const absolutePath =
|
|
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 {
|
|
2615
|
+
const { output } = options;
|
|
2616
|
+
if (!fs4.existsSync(output)) {
|
|
2617
|
+
fs4.mkdirSync(output, { recursive: true });
|
|
2618
|
+
}
|
|
1609
2619
|
const config = {
|
|
1610
|
-
public:
|
|
2620
|
+
public: output
|
|
1611
2621
|
};
|
|
1612
|
-
this._server = http.createServer(
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
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.
|
|
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 */
|