bunki 0.12.1 → 0.13.1

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
@@ -33103,7 +33103,15 @@ async function parseMarkdownFile(filePath) {
33103
33103
  slug,
33104
33104
  url: `/${postYear}/${slug}/`,
33105
33105
  excerpt: data.excerpt || extractExcerpt(content),
33106
- html: sanitizedHtml
33106
+ html: sanitizedHtml,
33107
+ ...data.location && {
33108
+ location: {
33109
+ name: data.location.name,
33110
+ address: data.location.address,
33111
+ lat: data.location.lat,
33112
+ lng: data.location.lng
33113
+ }
33114
+ }
33107
33115
  };
33108
33116
  return { post, error: null };
33109
33117
  } catch (error) {
@@ -33311,6 +33319,24 @@ function generateBreadcrumbListSchema(options2) {
33311
33319
  }
33312
33320
  return breadcrumbs;
33313
33321
  }
33322
+ function generateCollectionPageSchema(options2) {
33323
+ const { title, description, url, posts, site } = options2;
33324
+ return {
33325
+ "@context": "https://schema.org",
33326
+ "@type": "CollectionPage",
33327
+ name: title,
33328
+ description,
33329
+ url,
33330
+ mainEntity: {
33331
+ "@type": "ItemList",
33332
+ itemListElement: posts.slice(0, 10).map((post, index) => ({
33333
+ "@type": "ListItem",
33334
+ position: index + 1,
33335
+ url: `${site.baseUrl}${post.url}`
33336
+ }))
33337
+ }
33338
+ };
33339
+ }
33314
33340
  function toScriptTag(jsonLd) {
33315
33341
  const json2 = JSON.stringify(jsonLd, null, 2);
33316
33342
  return `<script type="application/ld+json">
@@ -33482,6 +33508,7 @@ class SiteGenerator {
33482
33508
  this.generateIndexPage(),
33483
33509
  this.generatePostPages(),
33484
33510
  this.generateTagPages(),
33511
+ this.generateMapPage(),
33485
33512
  this.generateYearArchives(),
33486
33513
  this.generateRSSFeed(),
33487
33514
  this.generateSitemap(),
@@ -33517,12 +33544,25 @@ class SiteGenerator {
33517
33544
  const endIndex = startIndex + pageSize;
33518
33545
  const paginatedPosts = yearPosts.slice(startIndex, endIndex);
33519
33546
  const pagination = this.createPagination(yearPosts, page, pageSize, `/${year}/`);
33547
+ let jsonLd = "";
33548
+ if (page === 1) {
33549
+ const schema = generateCollectionPageSchema({
33550
+ title: `Posts from ${year}`,
33551
+ description: `Articles published in ${year}`,
33552
+ url: `${this.options.config.baseUrl}/${year}/`,
33553
+ posts: yearPosts,
33554
+ site: this.options.config
33555
+ });
33556
+ jsonLd = toScriptTag(schema);
33557
+ }
33520
33558
  const yearPageHtml = import_nunjucks.default.render("archive.njk", {
33521
33559
  site: this.options.config,
33522
33560
  posts: paginatedPosts,
33523
33561
  tags: this.getSortedTags(this.options.config.maxTagsOnHomepage),
33524
33562
  year,
33525
- pagination
33563
+ pagination,
33564
+ noindex: page > 2,
33565
+ jsonLd
33526
33566
  });
33527
33567
  if (page === 1) {
33528
33568
  await Bun.write(path5.join(yearDir, "index.html"), yearPageHtml);
@@ -33555,7 +33595,8 @@ class SiteGenerator {
33555
33595
  posts: paginatedPosts,
33556
33596
  tags: this.getSortedTags(this.options.config.maxTagsOnHomepage),
33557
33597
  pagination,
33558
- jsonLd
33598
+ jsonLd,
33599
+ noindex: page > 2
33559
33600
  });
33560
33601
  if (page === 1) {
33561
33602
  await Bun.write(path5.join(this.options.outputDir, "index.html"), pageHtml);
@@ -33609,11 +33650,25 @@ class SiteGenerator {
33609
33650
  posts: paginatedPosts
33610
33651
  };
33611
33652
  const pagination = this.createPagination(tagData.posts, page, pageSize, `/tags/${tagData.slug}/`);
33653
+ let jsonLd = "";
33654
+ if (page === 1) {
33655
+ const description = tagData.description || `Articles tagged with ${tagName}`;
33656
+ const schema = generateCollectionPageSchema({
33657
+ title: `${tagName}`,
33658
+ description,
33659
+ url: `${this.options.config.baseUrl}/tags/${tagData.slug}/`,
33660
+ posts: tagData.posts,
33661
+ site: this.options.config
33662
+ });
33663
+ jsonLd = toScriptTag(schema);
33664
+ }
33612
33665
  const tagPageHtml = import_nunjucks.default.render("tag.njk", {
33613
33666
  site: this.options.config,
33614
33667
  tag: paginatedTagData,
33615
33668
  tags: Object.values(this.site.tags),
33616
- pagination
33669
+ pagination,
33670
+ noindex: page > 2,
33671
+ jsonLd
33617
33672
  });
33618
33673
  if (page === 1) {
33619
33674
  await Bun.write(path5.join(tagDir, "index.html"), tagPageHtml);
@@ -33625,6 +33680,24 @@ class SiteGenerator {
33625
33680
  }
33626
33681
  }
33627
33682
  }
33683
+ async generateMapPage() {
33684
+ try {
33685
+ const mapDir = path5.join(this.options.outputDir, "map");
33686
+ await ensureDir(mapDir);
33687
+ const mapHtml = import_nunjucks.default.render("map.njk", {
33688
+ site: this.options.config,
33689
+ posts: this.site.posts
33690
+ });
33691
+ await Bun.write(path5.join(mapDir, "index.html"), mapHtml);
33692
+ console.log("Generated map page");
33693
+ } catch (error) {
33694
+ if (error instanceof Error && error.message.includes("map.njk")) {
33695
+ console.log("No map.njk template found, skipping map page generation");
33696
+ } else {
33697
+ console.warn("Error generating map page:", error);
33698
+ }
33699
+ }
33700
+ }
33628
33701
  async generateStylesheet() {
33629
33702
  const cssConfig = this.options.config.css || getDefaultCSSConfig();
33630
33703
  if (!cssConfig.enabled) {
@@ -33749,8 +33822,13 @@ class SiteGenerator {
33749
33822
  itemXml += `
33750
33823
  <author>${config.authorEmail}</author>`;
33751
33824
  }
33825
+ let description = post.excerpt;
33826
+ if (featuredImage) {
33827
+ const absoluteImageUrl = featuredImage.startsWith("http") ? featuredImage : `${config.baseUrl}${featuredImage}`;
33828
+ description = `<img src="${this.escapeXml(absoluteImageUrl)}" alt="" style="max-width:100%; height:auto;" /><br/><br/>${post.excerpt}`;
33829
+ }
33752
33830
  itemXml += `
33753
- <description><![CDATA[${post.excerpt}]]></description>`;
33831
+ <description><![CDATA[${description}]]></description>`;
33754
33832
  if (post.tags.length > 0) {
33755
33833
  itemXml += `
33756
33834
  ${categoryTags}`;
@@ -33761,6 +33839,8 @@ ${categoryTags}`;
33761
33839
  const absoluteImageUrl = featuredImage.startsWith("http") ? featuredImage : `${config.baseUrl}${featuredImage}`;
33762
33840
  itemXml += `
33763
33841
  <media:thumbnail url="${this.escapeXml(absoluteImageUrl)}" />`;
33842
+ itemXml += `
33843
+ <enclosure url="${this.escapeXml(absoluteImageUrl)}" type="image/jpeg" length="0" />`;
33764
33844
  }
33765
33845
  itemXml += `
33766
33846
  </item>`;
@@ -33861,6 +33941,13 @@ ${rssItems}
33861
33941
  <changefreq>weekly</changefreq>
33862
33942
  <priority>0.5</priority>
33863
33943
  </url>
33944
+ `;
33945
+ sitemapContent += ` <url>
33946
+ <loc>${config.baseUrl}/map/</loc>
33947
+ <lastmod>${currentDate}</lastmod>
33948
+ <changefreq>weekly</changefreq>
33949
+ <priority>0.6</priority>
33950
+ </url>
33864
33951
  `;
33865
33952
  for (const [, tagData] of Object.entries(this.site.tags)) {
33866
33953
  const tagUrl = `${config.baseUrl}/tags/${tagData.slug}/`;
package/dist/index.js CHANGED
@@ -30721,7 +30721,15 @@ async function parseMarkdownFile(filePath) {
30721
30721
  slug,
30722
30722
  url: `/${postYear}/${slug}/`,
30723
30723
  excerpt: data.excerpt || extractExcerpt(content),
30724
- html: sanitizedHtml
30724
+ html: sanitizedHtml,
30725
+ ...data.location && {
30726
+ location: {
30727
+ name: data.location.name,
30728
+ address: data.location.address,
30729
+ lat: data.location.lat,
30730
+ lng: data.location.lng
30731
+ }
30732
+ }
30725
30733
  };
30726
30734
  return { post, error: null };
30727
30735
  } catch (error) {
@@ -31258,6 +31266,24 @@ function generateBreadcrumbListSchema(options2) {
31258
31266
  }
31259
31267
  return breadcrumbs;
31260
31268
  }
31269
+ function generateCollectionPageSchema(options2) {
31270
+ const { title, description, url, posts, site } = options2;
31271
+ return {
31272
+ "@context": "https://schema.org",
31273
+ "@type": "CollectionPage",
31274
+ name: title,
31275
+ description,
31276
+ url,
31277
+ mainEntity: {
31278
+ "@type": "ItemList",
31279
+ itemListElement: posts.slice(0, 10).map((post, index) => ({
31280
+ "@type": "ListItem",
31281
+ position: index + 1,
31282
+ url: `${site.baseUrl}${post.url}`
31283
+ }))
31284
+ }
31285
+ };
31286
+ }
31261
31287
  function toScriptTag(jsonLd) {
31262
31288
  const json2 = JSON.stringify(jsonLd, null, 2);
31263
31289
  return `<script type="application/ld+json">
@@ -31429,6 +31455,7 @@ class SiteGenerator {
31429
31455
  this.generateIndexPage(),
31430
31456
  this.generatePostPages(),
31431
31457
  this.generateTagPages(),
31458
+ this.generateMapPage(),
31432
31459
  this.generateYearArchives(),
31433
31460
  this.generateRSSFeed(),
31434
31461
  this.generateSitemap(),
@@ -31464,12 +31491,25 @@ class SiteGenerator {
31464
31491
  const endIndex = startIndex + pageSize;
31465
31492
  const paginatedPosts = yearPosts.slice(startIndex, endIndex);
31466
31493
  const pagination = this.createPagination(yearPosts, page, pageSize, `/${year}/`);
31494
+ let jsonLd = "";
31495
+ if (page === 1) {
31496
+ const schema = generateCollectionPageSchema({
31497
+ title: `Posts from ${year}`,
31498
+ description: `Articles published in ${year}`,
31499
+ url: `${this.options.config.baseUrl}/${year}/`,
31500
+ posts: yearPosts,
31501
+ site: this.options.config
31502
+ });
31503
+ jsonLd = toScriptTag(schema);
31504
+ }
31467
31505
  const yearPageHtml = import_nunjucks.default.render("archive.njk", {
31468
31506
  site: this.options.config,
31469
31507
  posts: paginatedPosts,
31470
31508
  tags: this.getSortedTags(this.options.config.maxTagsOnHomepage),
31471
31509
  year,
31472
- pagination
31510
+ pagination,
31511
+ noindex: page > 2,
31512
+ jsonLd
31473
31513
  });
31474
31514
  if (page === 1) {
31475
31515
  await Bun.write(path5.join(yearDir, "index.html"), yearPageHtml);
@@ -31502,7 +31542,8 @@ class SiteGenerator {
31502
31542
  posts: paginatedPosts,
31503
31543
  tags: this.getSortedTags(this.options.config.maxTagsOnHomepage),
31504
31544
  pagination,
31505
- jsonLd
31545
+ jsonLd,
31546
+ noindex: page > 2
31506
31547
  });
31507
31548
  if (page === 1) {
31508
31549
  await Bun.write(path5.join(this.options.outputDir, "index.html"), pageHtml);
@@ -31556,11 +31597,25 @@ class SiteGenerator {
31556
31597
  posts: paginatedPosts
31557
31598
  };
31558
31599
  const pagination = this.createPagination(tagData.posts, page, pageSize, `/tags/${tagData.slug}/`);
31600
+ let jsonLd = "";
31601
+ if (page === 1) {
31602
+ const description = tagData.description || `Articles tagged with ${tagName}`;
31603
+ const schema = generateCollectionPageSchema({
31604
+ title: `${tagName}`,
31605
+ description,
31606
+ url: `${this.options.config.baseUrl}/tags/${tagData.slug}/`,
31607
+ posts: tagData.posts,
31608
+ site: this.options.config
31609
+ });
31610
+ jsonLd = toScriptTag(schema);
31611
+ }
31559
31612
  const tagPageHtml = import_nunjucks.default.render("tag.njk", {
31560
31613
  site: this.options.config,
31561
31614
  tag: paginatedTagData,
31562
31615
  tags: Object.values(this.site.tags),
31563
- pagination
31616
+ pagination,
31617
+ noindex: page > 2,
31618
+ jsonLd
31564
31619
  });
31565
31620
  if (page === 1) {
31566
31621
  await Bun.write(path5.join(tagDir, "index.html"), tagPageHtml);
@@ -31572,6 +31627,24 @@ class SiteGenerator {
31572
31627
  }
31573
31628
  }
31574
31629
  }
31630
+ async generateMapPage() {
31631
+ try {
31632
+ const mapDir = path5.join(this.options.outputDir, "map");
31633
+ await ensureDir(mapDir);
31634
+ const mapHtml = import_nunjucks.default.render("map.njk", {
31635
+ site: this.options.config,
31636
+ posts: this.site.posts
31637
+ });
31638
+ await Bun.write(path5.join(mapDir, "index.html"), mapHtml);
31639
+ console.log("Generated map page");
31640
+ } catch (error) {
31641
+ if (error instanceof Error && error.message.includes("map.njk")) {
31642
+ console.log("No map.njk template found, skipping map page generation");
31643
+ } else {
31644
+ console.warn("Error generating map page:", error);
31645
+ }
31646
+ }
31647
+ }
31575
31648
  async generateStylesheet() {
31576
31649
  const cssConfig = this.options.config.css || getDefaultCSSConfig();
31577
31650
  if (!cssConfig.enabled) {
@@ -31696,8 +31769,13 @@ class SiteGenerator {
31696
31769
  itemXml += `
31697
31770
  <author>${config.authorEmail}</author>`;
31698
31771
  }
31772
+ let description = post.excerpt;
31773
+ if (featuredImage) {
31774
+ const absoluteImageUrl = featuredImage.startsWith("http") ? featuredImage : `${config.baseUrl}${featuredImage}`;
31775
+ description = `<img src="${this.escapeXml(absoluteImageUrl)}" alt="" style="max-width:100%; height:auto;" /><br/><br/>${post.excerpt}`;
31776
+ }
31699
31777
  itemXml += `
31700
- <description><![CDATA[${post.excerpt}]]></description>`;
31778
+ <description><![CDATA[${description}]]></description>`;
31701
31779
  if (post.tags.length > 0) {
31702
31780
  itemXml += `
31703
31781
  ${categoryTags}`;
@@ -31708,6 +31786,8 @@ ${categoryTags}`;
31708
31786
  const absoluteImageUrl = featuredImage.startsWith("http") ? featuredImage : `${config.baseUrl}${featuredImage}`;
31709
31787
  itemXml += `
31710
31788
  <media:thumbnail url="${this.escapeXml(absoluteImageUrl)}" />`;
31789
+ itemXml += `
31790
+ <enclosure url="${this.escapeXml(absoluteImageUrl)}" type="image/jpeg" length="0" />`;
31711
31791
  }
31712
31792
  itemXml += `
31713
31793
  </item>`;
@@ -31808,6 +31888,13 @@ ${rssItems}
31808
31888
  <changefreq>weekly</changefreq>
31809
31889
  <priority>0.5</priority>
31810
31890
  </url>
31891
+ `;
31892
+ sitemapContent += ` <url>
31893
+ <loc>${config.baseUrl}/map/</loc>
31894
+ <lastmod>${currentDate}</lastmod>
31895
+ <changefreq>weekly</changefreq>
31896
+ <priority>0.6</priority>
31897
+ </url>
31811
31898
  `;
31812
31899
  for (const [, tagData] of Object.entries(this.site.tags)) {
31813
31900
  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;
package/dist/types.d.ts CHANGED
@@ -1,6 +1,16 @@
1
1
  /**
2
2
  * Post object representing a single markdown file
3
3
  */
4
+ export interface Location {
5
+ /** Name of the location */
6
+ name: string;
7
+ /** Full address of the location */
8
+ address: string;
9
+ /** Latitude coordinate */
10
+ lat: number;
11
+ /** Longitude coordinate */
12
+ lng: number;
13
+ }
4
14
  export interface Post {
5
15
  /** Title of the post */
6
16
  title: string;
@@ -20,6 +30,8 @@ export interface Post {
20
30
  excerpt: string;
21
31
  /** Rendered HTML content */
22
32
  html: string;
33
+ /** Optional location data for map display */
34
+ location?: Location;
23
35
  }
24
36
  /**
25
37
  * Configuration for CSS processing
@@ -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.1",
3
+ "version": "0.13.1",
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",