docula 1.6.0 → 1.8.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.d.ts CHANGED
@@ -119,6 +119,8 @@ type DoculaConsoleArguments = {
119
119
  port: number | undefined;
120
120
  typescript: boolean;
121
121
  javascript: boolean;
122
+ overwrite: boolean;
123
+ downloadTarget: string;
122
124
  };
123
125
 
124
126
  type GithubData = {
@@ -363,6 +365,9 @@ declare class DoculaBuilder {
363
365
  buildRobotsPage(options: DoculaOptions): Promise<void>;
364
366
  buildSiteMapPage(data: DoculaData): Promise<void>;
365
367
  buildFeedPage(data: DoculaData): Promise<void>;
368
+ buildChangelogFeedJson(data: DoculaData): Promise<void>;
369
+ buildChangelogLatestFeedJson(data: DoculaData): Promise<void>;
370
+ private writeChangelogFeedJson;
366
371
  buildLlmsFiles(data: DoculaData): Promise<void>;
367
372
  private generateLlmsIndexContent;
368
373
  private generateLlmsFullContent;
@@ -500,6 +505,26 @@ declare class Docula {
500
505
  * @returns {void}
501
506
  */
502
507
  generateInit(sitePath: string, typescript?: boolean): void;
508
+ /**
509
+ * Copy the template's variables.css to the site directory.
510
+ * If the file already exists and overwrite is false, prints an error.
511
+ * @param {string} sitePath
512
+ * @param {string} templatePath
513
+ * @param {string} templateName
514
+ * @param {boolean} overwrite
515
+ * @returns {void}
516
+ */
517
+ downloadVariables(sitePath: string, templatePath: string, templateName: string, overwrite?: boolean): void;
518
+ /**
519
+ * Copy the full template directory to {sitePath}/templates/{outputName}/.
520
+ * If the directory already exists and overwrite is false, prints an error.
521
+ * @param {string} sitePath
522
+ * @param {string} templatePath
523
+ * @param {string} templateName
524
+ * @param {boolean} overwrite
525
+ * @returns {void}
526
+ */
527
+ downloadTemplate(sitePath: string, templatePath: string, templateName: string, overwrite?: boolean): void;
503
528
  /**
504
529
  * Get the version of the package
505
530
  * @returns {string}
package/dist/docula.js CHANGED
@@ -581,6 +581,9 @@ var DoculaConsole = class {
581
581
  );
582
582
  console.log(` ${green("help")} Print this help`);
583
583
  console.log(` ${green("version")} Print the version`);
584
+ console.log(
585
+ ` ${green("download")} Download template files to your site directory`
586
+ );
584
587
  console.log();
585
588
  console.log(bold(cyan(" Common Options:")));
586
589
  console.log(
@@ -619,6 +622,17 @@ var DoculaConsole = class {
619
622
  console.log(
620
623
  ` ${yellow("-b, --build")} Build the site before serving`
621
624
  );
625
+ console.log();
626
+ console.log(bold(cyan(" Download Options:")));
627
+ console.log(
628
+ ` ${yellow("variables")} Copy variables.css to your site directory`
629
+ );
630
+ console.log(
631
+ ` ${yellow("template")} Copy the full template to site/templates/<name>/`
632
+ );
633
+ console.log(
634
+ ` ${yellow("--overwrite")} Overwrite existing files if they already exist`
635
+ );
622
636
  }
623
637
  parseProcessArgv(argv) {
624
638
  const command = this.getCommand(argv);
@@ -676,6 +690,9 @@ var DoculaConsole = class {
676
690
  case "version": {
677
691
  return "version";
678
692
  }
693
+ case "download": {
694
+ return "download";
695
+ }
679
696
  }
680
697
  }
681
698
  }
@@ -690,7 +707,9 @@ var DoculaConsole = class {
690
707
  build: false,
691
708
  port: void 0,
692
709
  typescript: false,
693
- javascript: false
710
+ javascript: false,
711
+ overwrite: false,
712
+ downloadTarget: ""
694
713
  };
695
714
  for (let i = 0; i < argv.length; i++) {
696
715
  const argument = argv[i];
@@ -752,6 +771,36 @@ var DoculaConsole = class {
752
771
  arguments_.javascript = true;
753
772
  break;
754
773
  }
774
+ case "--overwrite": {
775
+ arguments_.overwrite = true;
776
+ break;
777
+ }
778
+ }
779
+ }
780
+ const downloadFlagsWithValues = /* @__PURE__ */ new Set([
781
+ "-s",
782
+ "--site",
783
+ "-o",
784
+ "--output",
785
+ "-p",
786
+ "--port",
787
+ "-t",
788
+ "--templatePath",
789
+ "-T",
790
+ "--template"
791
+ ]);
792
+ const downloadIndex = argv.indexOf("download");
793
+ if (downloadIndex !== -1) {
794
+ for (let i = downloadIndex + 1; i < argv.length; i++) {
795
+ const token = argv[i];
796
+ if (downloadFlagsWithValues.has(token)) {
797
+ i++;
798
+ continue;
799
+ }
800
+ if (!token.startsWith("-")) {
801
+ arguments_.downloadTarget = token;
802
+ break;
803
+ }
755
804
  }
756
805
  }
757
806
  return arguments_;
@@ -1184,6 +1233,10 @@ function resolveTemplatePath(templatePath, templateName) {
1184
1233
  }
1185
1234
 
1186
1235
  // src/builder.ts
1236
+ var writrOptions = {
1237
+ throwOnEmitError: false,
1238
+ throwOnEmptyListeners: false
1239
+ };
1187
1240
  var DoculaBuilder = class {
1188
1241
  _options = new DoculaOptions();
1189
1242
  _ecto;
@@ -1360,6 +1413,12 @@ var DoculaBuilder = class {
1360
1413
  await this.buildFeedPage(doculaData);
1361
1414
  this._console.fileBuilt("feed.xml");
1362
1415
  }
1416
+ if (doculaData.hasChangelog && doculaData.templates?.changelogEntry) {
1417
+ await this.buildChangelogFeedJson(doculaData);
1418
+ this._console.fileBuilt("changelog.json");
1419
+ await this.buildChangelogLatestFeedJson(doculaData);
1420
+ this._console.fileBuilt("changelog-latest.json");
1421
+ }
1363
1422
  if (doculaData.hasDocuments) {
1364
1423
  this._console.step("Building documentation pages...");
1365
1424
  await this.buildDocsPages(doculaData);
@@ -1580,6 +1639,12 @@ var DoculaBuilder = class {
1580
1639
  if (data.documents?.length) {
1581
1640
  urls.push({ url: `${data.siteUrl}${data.baseUrl}/feed.xml` });
1582
1641
  }
1642
+ if (data.hasChangelog && data.templates?.changelogEntry) {
1643
+ urls.push({ url: `${data.siteUrl}${data.baseUrl}/changelog.json` });
1644
+ urls.push({
1645
+ url: `${data.siteUrl}${data.baseUrl}/changelog-latest.json`
1646
+ });
1647
+ }
1583
1648
  if (data.openApiUrl && data.templates?.api) {
1584
1649
  urls.push({
1585
1650
  url: `${data.siteUrl}${data.apiUrl}`
@@ -1650,7 +1715,7 @@ var DoculaBuilder = class {
1650
1715
  data.siteUrl,
1651
1716
  `${data.baseUrl}${this.normalizePathForUrl(document.urlPath)}`
1652
1717
  );
1653
- const summary = document.description || this.summarizeMarkdown(new Writr(document.content).body);
1718
+ const summary = document.description || this.summarizeMarkdown(new Writr(document.content, writrOptions).body);
1654
1719
  xml += "<item>";
1655
1720
  xml += `<title>${this.escapeXml(itemTitle)}</title>`;
1656
1721
  xml += `<link>${this.escapeXml(itemLink)}</link>`;
@@ -1663,6 +1728,73 @@ var DoculaBuilder = class {
1663
1728
  await fs3.promises.mkdir(data.output, { recursive: true });
1664
1729
  await fs3.promises.writeFile(feedPath, xml, "utf8");
1665
1730
  }
1731
+ async buildChangelogFeedJson(data) {
1732
+ const entries = data.changelogEntries;
1733
+ if (!entries?.length) {
1734
+ return;
1735
+ }
1736
+ await this.writeChangelogFeedJson(data, entries, "changelog.json");
1737
+ }
1738
+ async buildChangelogLatestFeedJson(data) {
1739
+ const entries = data.changelogEntries;
1740
+ if (!entries?.length) {
1741
+ return;
1742
+ }
1743
+ const latestEntries = entries.slice(0, this.options.changelogPerPage);
1744
+ await this.writeChangelogFeedJson(
1745
+ data,
1746
+ latestEntries,
1747
+ "changelog-latest.json"
1748
+ );
1749
+ }
1750
+ async writeChangelogFeedJson(data, entries, filename) {
1751
+ const feedUrl = this.buildAbsoluteSiteUrl(
1752
+ data.siteUrl,
1753
+ `${data.baseUrl}/${filename}`
1754
+ );
1755
+ const homeUrl = this.buildAbsoluteSiteUrl(data.siteUrl, `${data.baseUrl}/`);
1756
+ const items = entries.map((entry) => {
1757
+ const itemUrl = this.buildAbsoluteSiteUrl(
1758
+ data.siteUrl,
1759
+ `${data.changelogUrl}/${entry.slug}/`
1760
+ );
1761
+ const item = {
1762
+ id: entry.slug,
1763
+ title: entry.title,
1764
+ url: itemUrl,
1765
+ date_published: entry.date,
1766
+ date_modified: entry.lastModified,
1767
+ summary: entry.preview
1768
+ };
1769
+ if (entry.generatedHtml) {
1770
+ item.content_html = entry.generatedHtml;
1771
+ }
1772
+ if (entry.content) {
1773
+ item.content_text = entry.content;
1774
+ }
1775
+ if (entry.tag) {
1776
+ item.tags = [entry.tag];
1777
+ }
1778
+ if (entry.previewImage) {
1779
+ item.image = entry.previewImage;
1780
+ }
1781
+ return item;
1782
+ });
1783
+ const feed = {
1784
+ version: "https://jsonfeed.org/version/1.1",
1785
+ title: data.siteTitle,
1786
+ description: data.siteDescription,
1787
+ home_page_url: homeUrl,
1788
+ feed_url: feedUrl,
1789
+ items
1790
+ };
1791
+ await fs3.promises.mkdir(data.output, { recursive: true });
1792
+ await fs3.promises.writeFile(
1793
+ `${data.output}/${filename}`,
1794
+ JSON.stringify(feed, null, 2),
1795
+ "utf8"
1796
+ );
1797
+ }
1666
1798
  async buildLlmsFiles(data) {
1667
1799
  if (!this.options.enableLlmsTxt) {
1668
1800
  return;
@@ -1768,7 +1900,10 @@ var DoculaBuilder = class {
1768
1900
  data.siteUrl,
1769
1901
  `${data.baseUrl}${this.normalizePathForUrl(document.urlPath)}`
1770
1902
  );
1771
- const markdownBody = new Writr(document.content).body.trim();
1903
+ const markdownBody = new Writr(
1904
+ document.content,
1905
+ writrOptions
1906
+ ).body.trim();
1772
1907
  lines.push("");
1773
1908
  lines.push(`### ${document.navTitle}`);
1774
1909
  lines.push(`URL: ${documentUrl}`);
@@ -2010,7 +2145,7 @@ var DoculaBuilder = class {
2010
2145
  `${data.sitePath}/README.md`,
2011
2146
  "utf8"
2012
2147
  );
2013
- htmlReadme = await new Writr(readmeContent).render();
2148
+ htmlReadme = await new Writr(readmeContent, writrOptions).render();
2014
2149
  }
2015
2150
  return htmlReadme;
2016
2151
  }
@@ -2018,7 +2153,7 @@ var DoculaBuilder = class {
2018
2153
  const announcementPath = `${data.sitePath}/announcement.md`;
2019
2154
  if (fs3.existsSync(announcementPath)) {
2020
2155
  const announcementContent = fs3.readFileSync(announcementPath, "utf8");
2021
- return new Writr(announcementContent).render();
2156
+ return new Writr(announcementContent, writrOptions).render();
2022
2157
  }
2023
2158
  return void 0;
2024
2159
  }
@@ -2074,12 +2209,16 @@ var DoculaBuilder = class {
2074
2209
  }
2075
2210
  if (apiSpec) {
2076
2211
  apiSpec.info.description = new Writr(
2077
- apiSpec.info.description
2212
+ apiSpec.info.description,
2213
+ writrOptions
2078
2214
  ).renderSync();
2079
2215
  for (const group of apiSpec.groups) {
2080
- group.description = new Writr(group.description).renderSync();
2216
+ group.description = new Writr(
2217
+ group.description,
2218
+ writrOptions
2219
+ ).renderSync();
2081
2220
  for (const op of group.operations) {
2082
- op.description = new Writr(op.description).renderSync();
2221
+ op.description = new Writr(op.description, writrOptions).renderSync();
2083
2222
  }
2084
2223
  }
2085
2224
  }
@@ -2146,7 +2285,7 @@ var DoculaBuilder = class {
2146
2285
  }
2147
2286
  parseChangelogEntry(filePath) {
2148
2287
  const fileContent = fs3.readFileSync(filePath, "utf8");
2149
- const writr = new Writr(fileContent);
2288
+ const writr = new Writr(fileContent, writrOptions);
2150
2289
  const matterData = writr.frontMatter;
2151
2290
  const markdownContent = writr.body;
2152
2291
  const fileName = path5.basename(filePath, path5.extname(filePath));
@@ -2178,7 +2317,9 @@ var DoculaBuilder = class {
2178
2317
  tagClass,
2179
2318
  slug,
2180
2319
  content: markdownContent,
2181
- generatedHtml: new Writr(markdownContent).renderSync({ mdx: isMdx }),
2320
+ generatedHtml: new Writr(markdownContent, writrOptions).renderSync({
2321
+ mdx: isMdx
2322
+ }),
2182
2323
  preview: this.generateChangelogPreview(markdownContent, 500, isMdx),
2183
2324
  previewImage,
2184
2325
  urlPath: `/${this.options.changelogPath}/${slug}/index.html`,
@@ -2193,7 +2334,7 @@ var DoculaBuilder = class {
2193
2334
  cleaned = cleaned.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1");
2194
2335
  cleaned = cleaned.replace(/^\n+/, "").trim();
2195
2336
  if (cleaned.length <= minLength) {
2196
- return new Writr(cleaned).renderSync({ mdx });
2337
+ return new Writr(cleaned, writrOptions).renderSync({ mdx });
2197
2338
  }
2198
2339
  const searchArea = cleaned.slice(0, maxLength);
2199
2340
  let splitIndex = -1;
@@ -2236,7 +2377,7 @@ var DoculaBuilder = class {
2236
2377
  }
2237
2378
  if (splitIndex > 0) {
2238
2379
  const truncated2 = cleaned.slice(0, splitIndex).trim();
2239
- return new Writr(truncated2).renderSync({ mdx });
2380
+ return new Writr(truncated2, writrOptions).renderSync({ mdx });
2240
2381
  }
2241
2382
  let truncated = cleaned.slice(0, maxLength);
2242
2383
  const lastSpace = truncated.lastIndexOf(" ");
@@ -2244,7 +2385,7 @@ var DoculaBuilder = class {
2244
2385
  truncated = truncated.slice(0, lastSpace);
2245
2386
  }
2246
2387
  truncated += "...";
2247
- return new Writr(truncated).renderSync({ mdx });
2388
+ return new Writr(truncated, writrOptions).renderSync({ mdx });
2248
2389
  }
2249
2390
  convertReleaseToChangelogEntry(release) {
2250
2391
  const tagName = release.tag_name ?? "";
@@ -2276,7 +2417,7 @@ var DoculaBuilder = class {
2276
2417
  tagClass,
2277
2418
  slug,
2278
2419
  content: body,
2279
- generatedHtml: new Writr(body).renderSync(),
2420
+ generatedHtml: new Writr(body, writrOptions).renderSync(),
2280
2421
  preview: this.generateChangelogPreview(body),
2281
2422
  urlPath: `/${this.options.changelogPath}/${slug}/index.html`,
2282
2423
  lastModified: dateString
@@ -2497,7 +2638,7 @@ var DoculaBuilder = class {
2497
2638
  }
2498
2639
  parseDocumentData(documentPath) {
2499
2640
  const documentContent = fs3.readFileSync(documentPath, "utf8");
2500
- const writr = new Writr(documentContent);
2641
+ const writr = new Writr(documentContent, writrOptions);
2501
2642
  const matterData = writr.frontMatter;
2502
2643
  let markdownContent = writr.body;
2503
2644
  markdownContent = markdownContent.replace(/^# .*\n/, "");
@@ -2537,7 +2678,7 @@ ${markdownContent.slice(firstH2)}`;
2537
2678
  keywords: matterData.keywords ?? [],
2538
2679
  content: documentContent,
2539
2680
  markdown: markdownContent,
2540
- generatedHtml: new Writr(markdownContent).renderSync({
2681
+ generatedHtml: new Writr(markdownContent, writrOptions).renderSync({
2541
2682
  toc: true,
2542
2683
  mdx: isMdx
2543
2684
  }),
@@ -3262,6 +3403,34 @@ var Docula = class {
3262
3403
  await this.serve(this.options);
3263
3404
  break;
3264
3405
  }
3406
+ case "download": {
3407
+ switch (consoleProcess.args.downloadTarget) {
3408
+ case "variables": {
3409
+ this.downloadVariables(
3410
+ this.options.sitePath,
3411
+ this.options.templatePath,
3412
+ this.options.template,
3413
+ consoleProcess.args.overwrite
3414
+ );
3415
+ break;
3416
+ }
3417
+ case "template": {
3418
+ this.downloadTemplate(
3419
+ this.options.sitePath,
3420
+ this.options.templatePath,
3421
+ this.options.template,
3422
+ consoleProcess.args.overwrite
3423
+ );
3424
+ break;
3425
+ }
3426
+ default: {
3427
+ this._console.error(
3428
+ "Please specify a download target: 'variables' or 'template'"
3429
+ );
3430
+ }
3431
+ }
3432
+ break;
3433
+ }
3265
3434
  default: {
3266
3435
  await this.runBuild(consoleProcess.args.clean);
3267
3436
  break;
@@ -3314,6 +3483,56 @@ var Docula = class {
3314
3483
  `docula initialized. Please update the ${doculaConfigFile} file with your site information. In addition, you can replace the image and favicon.`
3315
3484
  );
3316
3485
  }
3486
+ /**
3487
+ * Copy the template's variables.css to the site directory.
3488
+ * If the file already exists and overwrite is false, prints an error.
3489
+ * @param {string} sitePath
3490
+ * @param {string} templatePath
3491
+ * @param {string} templateName
3492
+ * @param {boolean} overwrite
3493
+ * @returns {void}
3494
+ */
3495
+ downloadVariables(sitePath, templatePath, templateName, overwrite = false) {
3496
+ const resolvedTemplatePath = resolveTemplatePath(
3497
+ templatePath,
3498
+ templateName
3499
+ );
3500
+ const source = path6.join(resolvedTemplatePath, "css", "variables.css");
3501
+ const dest = path6.join(sitePath, "variables.css");
3502
+ if (fs4.existsSync(dest) && !overwrite) {
3503
+ this._console.error(
3504
+ `variables.css already exists at ${dest}. Use --overwrite to replace it.`
3505
+ );
3506
+ return;
3507
+ }
3508
+ fs4.copyFileSync(source, dest);
3509
+ this._console.success(`variables.css copied to ${dest}`);
3510
+ }
3511
+ /**
3512
+ * Copy the full template directory to {sitePath}/templates/{outputName}/.
3513
+ * If the directory already exists and overwrite is false, prints an error.
3514
+ * @param {string} sitePath
3515
+ * @param {string} templatePath
3516
+ * @param {string} templateName
3517
+ * @param {boolean} overwrite
3518
+ * @returns {void}
3519
+ */
3520
+ downloadTemplate(sitePath, templatePath, templateName, overwrite = false) {
3521
+ const resolvedTemplatePath = resolveTemplatePath(
3522
+ templatePath,
3523
+ templateName
3524
+ );
3525
+ const outputName = templatePath ? path6.basename(resolvedTemplatePath) : templateName;
3526
+ const dest = path6.join(sitePath, "templates", outputName);
3527
+ if (fs4.existsSync(dest) && !overwrite) {
3528
+ this._console.error(
3529
+ `Template already exists at ${dest}. Use --overwrite to replace it.`
3530
+ );
3531
+ return;
3532
+ }
3533
+ fs4.cpSync(resolvedTemplatePath, dest, { recursive: true, force: true });
3534
+ this._console.success(`Template copied to ${dest}`);
3535
+ }
3317
3536
  /**
3318
3537
  * Get the version of the package
3319
3538
  * @returns {string}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docula",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Beautiful Website for Your Projects",
5
5
  "type": "module",
6
6
  "main": "./dist/docula.js",
@@ -40,31 +40,31 @@
40
40
  "docula": "./bin/docula.js"
41
41
  },
42
42
  "dependencies": {
43
- "@cacheable/net": "^2.0.5",
43
+ "@cacheable/net": "^2.0.6",
44
44
  "colorette": "^2.0.20",
45
- "ecto": "^4.8.2",
45
+ "ecto": "^4.8.3",
46
46
  "feed": "^5.2.0",
47
47
  "hashery": "^1.5.0",
48
48
  "jiti": "^2.6.1",
49
- "serve-handler": "^6.1.6",
49
+ "serve-handler": "^6.1.7",
50
50
  "update-notifier": "^7.3.1",
51
- "writr": "^5.0.3"
51
+ "writr": "^6.0.1"
52
52
  },
53
53
  "devDependencies": {
54
- "@biomejs/biome": "^2.4.2",
54
+ "@biomejs/biome": "^2.4.7",
55
55
  "@playwright/test": "^1.58.2",
56
56
  "@types/express": "^5.0.6",
57
57
  "@types/js-yaml": "^4.0.9",
58
- "@types/node": "^25.2.3",
58
+ "@types/node": "^25.5.0",
59
59
  "@types/serve-handler": "^6.1.4",
60
60
  "@types/update-notifier": "^6.0.8",
61
- "@vitest/coverage-v8": "^4.0.18",
61
+ "@vitest/coverage-v8": "^4.1.0",
62
62
  "dotenv": "^17.3.1",
63
63
  "rimraf": "^6.1.3",
64
64
  "tsup": "^8.5.1",
65
65
  "tsx": "^4.21.0",
66
66
  "typescript": "^5.9.3",
67
- "vitest": "^4.0.18"
67
+ "vitest": "^4.1.0"
68
68
  },
69
69
  "files": [
70
70
  "dist",