bunki 0.12.0 → 0.13.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/cli.js CHANGED
@@ -33206,7 +33206,7 @@ function generateOrganizationSchema(site) {
33206
33206
  "@context": "https://schema.org",
33207
33207
  "@type": "Organization",
33208
33208
  name: site.title,
33209
- url: site.baseUrl
33209
+ url: `${site.baseUrl}/`
33210
33210
  };
33211
33211
  if (site.description) {
33212
33212
  organization.description = site.description;
@@ -33239,7 +33239,7 @@ function generateBlogPostingSchema(options2) {
33239
33239
  blogPosting.publisher = {
33240
33240
  "@type": "Organization",
33241
33241
  name: site.title,
33242
- url: site.baseUrl
33242
+ url: `${site.baseUrl}/`
33243
33243
  };
33244
33244
  if (imageUrl) {
33245
33245
  blogPosting.image = imageUrl;
@@ -33263,7 +33263,7 @@ function generateWebSiteSchema(options2) {
33263
33263
  "@context": "https://schema.org",
33264
33264
  "@type": "WebSite",
33265
33265
  name: site.title,
33266
- url: site.baseUrl
33266
+ url: `${site.baseUrl}/`
33267
33267
  };
33268
33268
  if (site.description) {
33269
33269
  webSite.description = site.description;
@@ -33298,7 +33298,7 @@ function generateBreadcrumbListSchema(options2) {
33298
33298
  "@type": "ListItem",
33299
33299
  position: 1,
33300
33300
  name: "Home",
33301
- item: site.baseUrl
33301
+ item: `${site.baseUrl}/`
33302
33302
  };
33303
33303
  breadcrumbs.itemListElement.push(homeItem);
33304
33304
  if (post) {
@@ -33311,6 +33311,24 @@ function generateBreadcrumbListSchema(options2) {
33311
33311
  }
33312
33312
  return breadcrumbs;
33313
33313
  }
33314
+ function generateCollectionPageSchema(options2) {
33315
+ const { title, description, url, posts, site } = options2;
33316
+ return {
33317
+ "@context": "https://schema.org",
33318
+ "@type": "CollectionPage",
33319
+ name: title,
33320
+ description,
33321
+ url,
33322
+ mainEntity: {
33323
+ "@type": "ItemList",
33324
+ itemListElement: posts.slice(0, 10).map((post, index) => ({
33325
+ "@type": "ListItem",
33326
+ position: index + 1,
33327
+ url: `${site.baseUrl}${post.url}`
33328
+ }))
33329
+ }
33330
+ };
33331
+ }
33314
33332
  function toScriptTag(jsonLd) {
33315
33333
  const json2 = JSON.stringify(jsonLd, null, 2);
33316
33334
  return `<script type="application/ld+json">
@@ -33482,6 +33500,7 @@ class SiteGenerator {
33482
33500
  this.generateIndexPage(),
33483
33501
  this.generatePostPages(),
33484
33502
  this.generateTagPages(),
33503
+ this.generateMapPage(),
33485
33504
  this.generateYearArchives(),
33486
33505
  this.generateRSSFeed(),
33487
33506
  this.generateSitemap(),
@@ -33517,12 +33536,25 @@ class SiteGenerator {
33517
33536
  const endIndex = startIndex + pageSize;
33518
33537
  const paginatedPosts = yearPosts.slice(startIndex, endIndex);
33519
33538
  const pagination = this.createPagination(yearPosts, page, pageSize, `/${year}/`);
33539
+ let jsonLd = "";
33540
+ if (page === 1) {
33541
+ const schema = generateCollectionPageSchema({
33542
+ title: `Posts from ${year}`,
33543
+ description: `Articles published in ${year}`,
33544
+ url: `${this.options.config.baseUrl}/${year}/`,
33545
+ posts: yearPosts,
33546
+ site: this.options.config
33547
+ });
33548
+ jsonLd = toScriptTag(schema);
33549
+ }
33520
33550
  const yearPageHtml = import_nunjucks.default.render("archive.njk", {
33521
33551
  site: this.options.config,
33522
33552
  posts: paginatedPosts,
33523
33553
  tags: this.getSortedTags(this.options.config.maxTagsOnHomepage),
33524
33554
  year,
33525
- pagination
33555
+ pagination,
33556
+ noindex: page > 2,
33557
+ jsonLd
33526
33558
  });
33527
33559
  if (page === 1) {
33528
33560
  await Bun.write(path5.join(yearDir, "index.html"), yearPageHtml);
@@ -33555,7 +33587,8 @@ class SiteGenerator {
33555
33587
  posts: paginatedPosts,
33556
33588
  tags: this.getSortedTags(this.options.config.maxTagsOnHomepage),
33557
33589
  pagination,
33558
- jsonLd
33590
+ jsonLd,
33591
+ noindex: page > 2
33559
33592
  });
33560
33593
  if (page === 1) {
33561
33594
  await Bun.write(path5.join(this.options.outputDir, "index.html"), pageHtml);
@@ -33609,11 +33642,25 @@ class SiteGenerator {
33609
33642
  posts: paginatedPosts
33610
33643
  };
33611
33644
  const pagination = this.createPagination(tagData.posts, page, pageSize, `/tags/${tagData.slug}/`);
33645
+ let jsonLd = "";
33646
+ if (page === 1) {
33647
+ const description = tagData.description || `Articles tagged with ${tagName}`;
33648
+ const schema = generateCollectionPageSchema({
33649
+ title: `${tagName}`,
33650
+ description,
33651
+ url: `${this.options.config.baseUrl}/tags/${tagData.slug}/`,
33652
+ posts: tagData.posts,
33653
+ site: this.options.config
33654
+ });
33655
+ jsonLd = toScriptTag(schema);
33656
+ }
33612
33657
  const tagPageHtml = import_nunjucks.default.render("tag.njk", {
33613
33658
  site: this.options.config,
33614
33659
  tag: paginatedTagData,
33615
33660
  tags: Object.values(this.site.tags),
33616
- pagination
33661
+ pagination,
33662
+ noindex: page > 2,
33663
+ jsonLd
33617
33664
  });
33618
33665
  if (page === 1) {
33619
33666
  await Bun.write(path5.join(tagDir, "index.html"), tagPageHtml);
@@ -33625,6 +33672,24 @@ class SiteGenerator {
33625
33672
  }
33626
33673
  }
33627
33674
  }
33675
+ async generateMapPage() {
33676
+ try {
33677
+ const mapDir = path5.join(this.options.outputDir, "map");
33678
+ await ensureDir(mapDir);
33679
+ const mapHtml = import_nunjucks.default.render("map.njk", {
33680
+ site: this.options.config,
33681
+ posts: this.site.posts
33682
+ });
33683
+ await Bun.write(path5.join(mapDir, "index.html"), mapHtml);
33684
+ console.log("Generated map page");
33685
+ } catch (error) {
33686
+ if (error instanceof Error && error.message.includes("map.njk")) {
33687
+ console.log("No map.njk template found, skipping map page generation");
33688
+ } else {
33689
+ console.warn("Error generating map page:", error);
33690
+ }
33691
+ }
33692
+ }
33628
33693
  async generateStylesheet() {
33629
33694
  const cssConfig = this.options.config.css || getDefaultCSSConfig();
33630
33695
  if (!cssConfig.enabled) {
@@ -33749,8 +33814,13 @@ class SiteGenerator {
33749
33814
  itemXml += `
33750
33815
  <author>${config.authorEmail}</author>`;
33751
33816
  }
33817
+ let description = post.excerpt;
33818
+ if (featuredImage) {
33819
+ const absoluteImageUrl = featuredImage.startsWith("http") ? featuredImage : `${config.baseUrl}${featuredImage}`;
33820
+ description = `<img src="${this.escapeXml(absoluteImageUrl)}" alt="" style="max-width:100%; height:auto;" /><br/><br/>${post.excerpt}`;
33821
+ }
33752
33822
  itemXml += `
33753
- <description><![CDATA[${post.excerpt}]]></description>`;
33823
+ <description><![CDATA[${description}]]></description>`;
33754
33824
  if (post.tags.length > 0) {
33755
33825
  itemXml += `
33756
33826
  ${categoryTags}`;
@@ -33761,6 +33831,8 @@ ${categoryTags}`;
33761
33831
  const absoluteImageUrl = featuredImage.startsWith("http") ? featuredImage : `${config.baseUrl}${featuredImage}`;
33762
33832
  itemXml += `
33763
33833
  <media:thumbnail url="${this.escapeXml(absoluteImageUrl)}" />`;
33834
+ itemXml += `
33835
+ <enclosure url="${this.escapeXml(absoluteImageUrl)}" type="image/jpeg" length="0" />`;
33764
33836
  }
33765
33837
  itemXml += `
33766
33838
  </item>`;
@@ -33769,7 +33841,7 @@ ${categoryTags}`;
33769
33841
  `);
33770
33842
  let channelXml = ` <channel>
33771
33843
  <title><![CDATA[${config.title}]]></title>
33772
- <link>${config.baseUrl}</link>
33844
+ <link>${config.baseUrl}/</link>
33773
33845
  <description><![CDATA[${config.description}]]></description>`;
33774
33846
  const language = config.rssLanguage || "en-US";
33775
33847
  channelXml += `
@@ -33861,6 +33933,13 @@ ${rssItems}
33861
33933
  <changefreq>weekly</changefreq>
33862
33934
  <priority>0.5</priority>
33863
33935
  </url>
33936
+ `;
33937
+ sitemapContent += ` <url>
33938
+ <loc>${config.baseUrl}/map/</loc>
33939
+ <lastmod>${currentDate}</lastmod>
33940
+ <changefreq>weekly</changefreq>
33941
+ <priority>0.6</priority>
33942
+ </url>
33864
33943
  `;
33865
33944
  for (const [, tagData] of Object.entries(this.site.tags)) {
33866
33945
  const tagUrl = `${config.baseUrl}/tags/${tagData.slug}/`;
package/dist/index.js CHANGED
@@ -31153,7 +31153,7 @@ function generateOrganizationSchema(site) {
31153
31153
  "@context": "https://schema.org",
31154
31154
  "@type": "Organization",
31155
31155
  name: site.title,
31156
- url: site.baseUrl
31156
+ url: `${site.baseUrl}/`
31157
31157
  };
31158
31158
  if (site.description) {
31159
31159
  organization.description = site.description;
@@ -31186,7 +31186,7 @@ function generateBlogPostingSchema(options2) {
31186
31186
  blogPosting.publisher = {
31187
31187
  "@type": "Organization",
31188
31188
  name: site.title,
31189
- url: site.baseUrl
31189
+ url: `${site.baseUrl}/`
31190
31190
  };
31191
31191
  if (imageUrl) {
31192
31192
  blogPosting.image = imageUrl;
@@ -31210,7 +31210,7 @@ function generateWebSiteSchema(options2) {
31210
31210
  "@context": "https://schema.org",
31211
31211
  "@type": "WebSite",
31212
31212
  name: site.title,
31213
- url: site.baseUrl
31213
+ url: `${site.baseUrl}/`
31214
31214
  };
31215
31215
  if (site.description) {
31216
31216
  webSite.description = site.description;
@@ -31245,7 +31245,7 @@ function generateBreadcrumbListSchema(options2) {
31245
31245
  "@type": "ListItem",
31246
31246
  position: 1,
31247
31247
  name: "Home",
31248
- item: site.baseUrl
31248
+ item: `${site.baseUrl}/`
31249
31249
  };
31250
31250
  breadcrumbs.itemListElement.push(homeItem);
31251
31251
  if (post) {
@@ -31258,6 +31258,24 @@ function generateBreadcrumbListSchema(options2) {
31258
31258
  }
31259
31259
  return breadcrumbs;
31260
31260
  }
31261
+ function generateCollectionPageSchema(options2) {
31262
+ const { title, description, url, posts, site } = options2;
31263
+ return {
31264
+ "@context": "https://schema.org",
31265
+ "@type": "CollectionPage",
31266
+ name: title,
31267
+ description,
31268
+ url,
31269
+ mainEntity: {
31270
+ "@type": "ItemList",
31271
+ itemListElement: posts.slice(0, 10).map((post, index) => ({
31272
+ "@type": "ListItem",
31273
+ position: index + 1,
31274
+ url: `${site.baseUrl}${post.url}`
31275
+ }))
31276
+ }
31277
+ };
31278
+ }
31261
31279
  function toScriptTag(jsonLd) {
31262
31280
  const json2 = JSON.stringify(jsonLd, null, 2);
31263
31281
  return `<script type="application/ld+json">
@@ -31429,6 +31447,7 @@ class SiteGenerator {
31429
31447
  this.generateIndexPage(),
31430
31448
  this.generatePostPages(),
31431
31449
  this.generateTagPages(),
31450
+ this.generateMapPage(),
31432
31451
  this.generateYearArchives(),
31433
31452
  this.generateRSSFeed(),
31434
31453
  this.generateSitemap(),
@@ -31464,12 +31483,25 @@ class SiteGenerator {
31464
31483
  const endIndex = startIndex + pageSize;
31465
31484
  const paginatedPosts = yearPosts.slice(startIndex, endIndex);
31466
31485
  const pagination = this.createPagination(yearPosts, page, pageSize, `/${year}/`);
31486
+ let jsonLd = "";
31487
+ if (page === 1) {
31488
+ const schema = generateCollectionPageSchema({
31489
+ title: `Posts from ${year}`,
31490
+ description: `Articles published in ${year}`,
31491
+ url: `${this.options.config.baseUrl}/${year}/`,
31492
+ posts: yearPosts,
31493
+ site: this.options.config
31494
+ });
31495
+ jsonLd = toScriptTag(schema);
31496
+ }
31467
31497
  const yearPageHtml = import_nunjucks.default.render("archive.njk", {
31468
31498
  site: this.options.config,
31469
31499
  posts: paginatedPosts,
31470
31500
  tags: this.getSortedTags(this.options.config.maxTagsOnHomepage),
31471
31501
  year,
31472
- pagination
31502
+ pagination,
31503
+ noindex: page > 2,
31504
+ jsonLd
31473
31505
  });
31474
31506
  if (page === 1) {
31475
31507
  await Bun.write(path5.join(yearDir, "index.html"), yearPageHtml);
@@ -31502,7 +31534,8 @@ class SiteGenerator {
31502
31534
  posts: paginatedPosts,
31503
31535
  tags: this.getSortedTags(this.options.config.maxTagsOnHomepage),
31504
31536
  pagination,
31505
- jsonLd
31537
+ jsonLd,
31538
+ noindex: page > 2
31506
31539
  });
31507
31540
  if (page === 1) {
31508
31541
  await Bun.write(path5.join(this.options.outputDir, "index.html"), pageHtml);
@@ -31556,11 +31589,25 @@ class SiteGenerator {
31556
31589
  posts: paginatedPosts
31557
31590
  };
31558
31591
  const pagination = this.createPagination(tagData.posts, page, pageSize, `/tags/${tagData.slug}/`);
31592
+ let jsonLd = "";
31593
+ if (page === 1) {
31594
+ const description = tagData.description || `Articles tagged with ${tagName}`;
31595
+ const schema = generateCollectionPageSchema({
31596
+ title: `${tagName}`,
31597
+ description,
31598
+ url: `${this.options.config.baseUrl}/tags/${tagData.slug}/`,
31599
+ posts: tagData.posts,
31600
+ site: this.options.config
31601
+ });
31602
+ jsonLd = toScriptTag(schema);
31603
+ }
31559
31604
  const tagPageHtml = import_nunjucks.default.render("tag.njk", {
31560
31605
  site: this.options.config,
31561
31606
  tag: paginatedTagData,
31562
31607
  tags: Object.values(this.site.tags),
31563
- pagination
31608
+ pagination,
31609
+ noindex: page > 2,
31610
+ jsonLd
31564
31611
  });
31565
31612
  if (page === 1) {
31566
31613
  await Bun.write(path5.join(tagDir, "index.html"), tagPageHtml);
@@ -31572,6 +31619,24 @@ class SiteGenerator {
31572
31619
  }
31573
31620
  }
31574
31621
  }
31622
+ async generateMapPage() {
31623
+ try {
31624
+ const mapDir = path5.join(this.options.outputDir, "map");
31625
+ await ensureDir(mapDir);
31626
+ const mapHtml = import_nunjucks.default.render("map.njk", {
31627
+ site: this.options.config,
31628
+ posts: this.site.posts
31629
+ });
31630
+ await Bun.write(path5.join(mapDir, "index.html"), mapHtml);
31631
+ console.log("Generated map page");
31632
+ } catch (error) {
31633
+ if (error instanceof Error && error.message.includes("map.njk")) {
31634
+ console.log("No map.njk template found, skipping map page generation");
31635
+ } else {
31636
+ console.warn("Error generating map page:", error);
31637
+ }
31638
+ }
31639
+ }
31575
31640
  async generateStylesheet() {
31576
31641
  const cssConfig = this.options.config.css || getDefaultCSSConfig();
31577
31642
  if (!cssConfig.enabled) {
@@ -31696,8 +31761,13 @@ class SiteGenerator {
31696
31761
  itemXml += `
31697
31762
  <author>${config.authorEmail}</author>`;
31698
31763
  }
31764
+ let description = post.excerpt;
31765
+ if (featuredImage) {
31766
+ const absoluteImageUrl = featuredImage.startsWith("http") ? featuredImage : `${config.baseUrl}${featuredImage}`;
31767
+ description = `<img src="${this.escapeXml(absoluteImageUrl)}" alt="" style="max-width:100%; height:auto;" /><br/><br/>${post.excerpt}`;
31768
+ }
31699
31769
  itemXml += `
31700
- <description><![CDATA[${post.excerpt}]]></description>`;
31770
+ <description><![CDATA[${description}]]></description>`;
31701
31771
  if (post.tags.length > 0) {
31702
31772
  itemXml += `
31703
31773
  ${categoryTags}`;
@@ -31708,6 +31778,8 @@ ${categoryTags}`;
31708
31778
  const absoluteImageUrl = featuredImage.startsWith("http") ? featuredImage : `${config.baseUrl}${featuredImage}`;
31709
31779
  itemXml += `
31710
31780
  <media:thumbnail url="${this.escapeXml(absoluteImageUrl)}" />`;
31781
+ itemXml += `
31782
+ <enclosure url="${this.escapeXml(absoluteImageUrl)}" type="image/jpeg" length="0" />`;
31711
31783
  }
31712
31784
  itemXml += `
31713
31785
  </item>`;
@@ -31716,7 +31788,7 @@ ${categoryTags}`;
31716
31788
  `);
31717
31789
  let channelXml = ` <channel>
31718
31790
  <title><![CDATA[${config.title}]]></title>
31719
- <link>${config.baseUrl}</link>
31791
+ <link>${config.baseUrl}/</link>
31720
31792
  <description><![CDATA[${config.description}]]></description>`;
31721
31793
  const language = config.rssLanguage || "en-US";
31722
31794
  channelXml += `
@@ -31808,6 +31880,13 @@ ${rssItems}
31808
31880
  <changefreq>weekly</changefreq>
31809
31881
  <priority>0.5</priority>
31810
31882
  </url>
31883
+ `;
31884
+ sitemapContent += ` <url>
31885
+ <loc>${config.baseUrl}/map/</loc>
31886
+ <lastmod>${currentDate}</lastmod>
31887
+ <changefreq>weekly</changefreq>
31888
+ <priority>0.6</priority>
31889
+ </url>
31811
31890
  `;
31812
31891
  for (const [, tagData] of Object.entries(this.site.tags)) {
31813
31892
  const tagUrl = `${config.baseUrl}/tags/${tagData.slug}/`;
@@ -14,6 +14,7 @@ export declare class SiteGenerator {
14
14
  private generateIndexPage;
15
15
  private generatePostPages;
16
16
  private generateTagPages;
17
+ private generateMapPage;
17
18
  private generateStylesheet;
18
19
  private fallbackCSSGeneration;
19
20
  private copyStaticAssets;
@@ -58,6 +58,21 @@ export interface BreadcrumbListOptions {
58
58
  url: string;
59
59
  }>;
60
60
  }
61
+ /**
62
+ * Options for generating CollectionPage JSON-LD
63
+ */
64
+ export interface CollectionPageOptions {
65
+ /** Page title */
66
+ title: string;
67
+ /** Page description */
68
+ description: string;
69
+ /** Page URL */
70
+ url: string;
71
+ /** Posts to include in the collection */
72
+ posts: Post[];
73
+ /** Site configuration */
74
+ site: SiteConfig;
75
+ }
61
76
  /**
62
77
  * Generates a Person schema for author information
63
78
  *
@@ -136,6 +151,27 @@ export declare function generateWebSiteSchema(options: WebSiteOptions): SchemaOr
136
151
  * @see https://schema.org/BreadcrumbList
137
152
  */
138
153
  export declare function generateBreadcrumbListSchema(options: BreadcrumbListOptions): SchemaOrgThing;
154
+ /**
155
+ * Generates CollectionPage structured data for archive and tag pages
156
+ *
157
+ * This schema helps search engines understand that a page contains a collection
158
+ * of blog posts, improving indexing of archive, tag, and category pages.
159
+ *
160
+ * @param options - CollectionPage generation options
161
+ * @returns CollectionPage schema as JSON-LD object
162
+ *
163
+ * @example
164
+ * const jsonLd = generateCollectionPageSchema({
165
+ * title: "Posts from 2025",
166
+ * description: "All articles published in 2025",
167
+ * url: "https://example.com/2025/",
168
+ * posts: [...],
169
+ * site: { title: "My Blog", baseUrl: "https://example.com" }
170
+ * });
171
+ *
172
+ * @see https://schema.org/CollectionPage
173
+ */
174
+ export declare function generateCollectionPageSchema(options: CollectionPageOptions): SchemaOrgThing;
139
175
  /**
140
176
  * Converts a JSON-LD object to an HTML script tag string
141
177
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunki",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "An opinionated static site generator built with Bun featuring PostCSS integration and modern web development workflows",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",