bunki 0.5.2 → 0.6.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/index.js CHANGED
@@ -31210,29 +31210,85 @@ class SiteGenerator {
31210
31210
  console.log("Copied public files to site (including extensionless & dotfiles)");
31211
31211
  }
31212
31212
  }
31213
+ extractFirstImageUrl(html) {
31214
+ const imgRegex = /<img[^>]+src=["']([^"']+)["']/;
31215
+ const match = html.match(imgRegex);
31216
+ return match ? match[1] : null;
31217
+ }
31218
+ escapeXml(text) {
31219
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
31220
+ }
31213
31221
  async generateRSSFeed() {
31214
31222
  const posts = this.site.posts.slice(0, 15);
31215
31223
  const config = this.options.config;
31224
+ const now = this.getPacificDate(new Date);
31225
+ const latestPostDate = posts.length > 0 ? posts[0].date : now.toISOString();
31226
+ const lastBuildDate = this.formatRSSDate(latestPostDate);
31216
31227
  const rssItems = posts.map((post) => {
31217
31228
  const postUrl = `${config.baseUrl}${post.url}`;
31218
31229
  const pubDate = this.formatRSSDate(post.date);
31219
- return ` <item>
31230
+ const featuredImage = this.extractFirstImageUrl(post.html);
31231
+ const categoryTags = post.tags.map((tag) => ` <category>${this.escapeXml(tag)}</category>`).join(`
31232
+ `);
31233
+ let itemXml = ` <item>
31220
31234
  <title><![CDATA[${post.title}]]></title>
31221
31235
  <link>${postUrl}</link>
31222
- <guid>${postUrl}</guid>
31223
- <pubDate>${pubDate}</pubDate>
31224
- <description><![CDATA[${post.excerpt}]]></description>
31236
+ <guid isPermaLink="true">${postUrl}</guid>
31237
+ <pubDate>${pubDate}</pubDate>`;
31238
+ if (config.authorEmail && config.authorName) {
31239
+ itemXml += `
31240
+ <author>${config.authorEmail} (${config.authorName})</author>`;
31241
+ } else if (config.authorEmail) {
31242
+ itemXml += `
31243
+ <author>${config.authorEmail}</author>`;
31244
+ }
31245
+ itemXml += `
31246
+ <description><![CDATA[${post.excerpt}]]></description>`;
31247
+ if (post.tags.length > 0) {
31248
+ itemXml += `
31249
+ ${categoryTags}`;
31250
+ }
31251
+ itemXml += `
31252
+ <content:encoded><![CDATA[${post.html}]]></content:encoded>`;
31253
+ if (featuredImage) {
31254
+ const absoluteImageUrl = featuredImage.startsWith("http") ? featuredImage : `${config.baseUrl}${featuredImage}`;
31255
+ itemXml += `
31256
+ <media:thumbnail url="${this.escapeXml(absoluteImageUrl)}" />`;
31257
+ }
31258
+ itemXml += `
31225
31259
  </item>`;
31260
+ return itemXml;
31226
31261
  }).join(`
31227
31262
  `);
31228
- const rssContent = `<?xml version="1.0" encoding="UTF-8"?>
31229
- <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
31230
- <channel>
31263
+ let channelXml = ` <channel>
31231
31264
  <title><![CDATA[${config.title}]]></title>
31232
- <description><![CDATA[${config.description}]]></description>
31233
31265
  <link>${config.baseUrl}</link>
31234
- <atom:link href="${config.baseUrl}/feed.xml" rel="self" type="application/rss+xml" />
31235
- <lastBuildDate>${this.formatRSSDate(this.getPacificDate(new Date).toISOString())}</lastBuildDate>
31266
+ <description><![CDATA[${config.description}]]></description>`;
31267
+ const language = config.rssLanguage || "en-US";
31268
+ channelXml += `
31269
+ <language>${language}</language>`;
31270
+ if (config.authorEmail && config.authorName) {
31271
+ channelXml += `
31272
+ <managingEditor>${config.authorEmail} (${config.authorName})</managingEditor>`;
31273
+ } else if (config.authorEmail) {
31274
+ channelXml += `
31275
+ <managingEditor>${config.authorEmail}</managingEditor>`;
31276
+ }
31277
+ if (config.webMaster) {
31278
+ channelXml += `
31279
+ <webMaster>${config.webMaster}</webMaster>`;
31280
+ }
31281
+ if (config.copyright) {
31282
+ channelXml += `
31283
+ <copyright><![CDATA[${config.copyright}]]></copyright>`;
31284
+ }
31285
+ channelXml += `
31286
+ <pubDate>${this.formatRSSDate(latestPostDate)}</pubDate>
31287
+ <lastBuildDate>${lastBuildDate}</lastBuildDate>
31288
+ <atom:link href="${config.baseUrl}/feed.xml" rel="self" type="application/rss+xml" />`;
31289
+ const rssContent = `<?xml version="1.0" encoding="UTF-8"?>
31290
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/">
31291
+ ${channelXml}
31236
31292
  ${rssItems}
31237
31293
  </channel>
31238
31294
  </rss>`;
@@ -16,6 +16,14 @@ export declare class SiteGenerator {
16
16
  private generateStylesheet;
17
17
  private fallbackCSSGeneration;
18
18
  private copyStaticAssets;
19
+ /**
20
+ * Extract the first image URL from HTML content
21
+ */
22
+ private extractFirstImageUrl;
23
+ /**
24
+ * Escape special characters in XML text to prevent CDATA issues
25
+ */
26
+ private escapeXml;
19
27
  private generateRSSFeed;
20
28
  private generateSitemap;
21
29
  private generateSitemapIndex;
package/dist/types.d.ts CHANGED
@@ -58,6 +58,16 @@ export interface SiteConfig {
58
58
  maxTagsOnHomepage?: number;
59
59
  /** Optional list of domains to exclude from nofollow attribute. Links to these domains will have follow attribute. */
60
60
  noFollowExceptions?: string[];
61
+ /** RSS feed language code (default: en-US) */
62
+ rssLanguage?: string;
63
+ /** Author name for RSS feed (used in managingEditor field) */
64
+ authorName?: string;
65
+ /** Author email for RSS feed (used in managingEditor field) */
66
+ authorEmail?: string;
67
+ /** Web master email for RSS feed (optional) */
68
+ webMaster?: string;
69
+ /** Copyright statement for RSS feed (e.g., "Copyright © 2025 Your Site Name") */
70
+ copyright?: string;
61
71
  /** Additional custom configuration options */
62
72
  [key: string]: any;
63
73
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunki",
3
- "version": "0.5.2",
3
+ "version": "0.6.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",
@@ -59,15 +59,15 @@
59
59
  "slugify": "^1.6.6"
60
60
  },
61
61
  "devDependencies": {
62
- "@tailwindcss/postcss": "^4.1.14",
62
+ "@tailwindcss/postcss": "^4.1.15",
63
63
  "@types/nunjucks": "^3.2.6",
64
64
  "@types/sanitize-html": "^2.16.0",
65
65
  "autoprefixer": "^10.4.20",
66
66
  "bun-types": "^1.3.0",
67
67
  "husky": "^9.1.7",
68
- "lint-staged": "^16.2.4",
68
+ "lint-staged": "^16.2.5",
69
69
  "prettier": "^3.6.2",
70
- "tailwindcss": "^4.1.14",
70
+ "tailwindcss": "^4.1.15",
71
71
  "typescript": "^5.9.3"
72
72
  },
73
73
  "peerDependencies": {