bunki 0.5.3 → 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/cli.js CHANGED
@@ -33263,29 +33263,85 @@ class SiteGenerator {
33263
33263
  console.log("Copied public files to site (including extensionless & dotfiles)");
33264
33264
  }
33265
33265
  }
33266
+ extractFirstImageUrl(html) {
33267
+ const imgRegex = /<img[^>]+src=["']([^"']+)["']/;
33268
+ const match = html.match(imgRegex);
33269
+ return match ? match[1] : null;
33270
+ }
33271
+ escapeXml(text) {
33272
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
33273
+ }
33266
33274
  async generateRSSFeed() {
33267
33275
  const posts = this.site.posts.slice(0, 15);
33268
33276
  const config = this.options.config;
33277
+ const now = this.getPacificDate(new Date);
33278
+ const latestPostDate = posts.length > 0 ? posts[0].date : now.toISOString();
33279
+ const lastBuildDate = this.formatRSSDate(latestPostDate);
33269
33280
  const rssItems = posts.map((post) => {
33270
33281
  const postUrl = `${config.baseUrl}${post.url}`;
33271
33282
  const pubDate = this.formatRSSDate(post.date);
33272
- return ` <item>
33283
+ const featuredImage = this.extractFirstImageUrl(post.html);
33284
+ const categoryTags = post.tags.map((tag) => ` <category>${this.escapeXml(tag)}</category>`).join(`
33285
+ `);
33286
+ let itemXml = ` <item>
33273
33287
  <title><![CDATA[${post.title}]]></title>
33274
33288
  <link>${postUrl}</link>
33275
- <guid>${postUrl}</guid>
33276
- <pubDate>${pubDate}</pubDate>
33277
- <description><![CDATA[${post.excerpt}]]></description>
33289
+ <guid isPermaLink="true">${postUrl}</guid>
33290
+ <pubDate>${pubDate}</pubDate>`;
33291
+ if (config.authorEmail && config.authorName) {
33292
+ itemXml += `
33293
+ <author>${config.authorEmail} (${config.authorName})</author>`;
33294
+ } else if (config.authorEmail) {
33295
+ itemXml += `
33296
+ <author>${config.authorEmail}</author>`;
33297
+ }
33298
+ itemXml += `
33299
+ <description><![CDATA[${post.excerpt}]]></description>`;
33300
+ if (post.tags.length > 0) {
33301
+ itemXml += `
33302
+ ${categoryTags}`;
33303
+ }
33304
+ itemXml += `
33305
+ <content:encoded><![CDATA[${post.html}]]></content:encoded>`;
33306
+ if (featuredImage) {
33307
+ const absoluteImageUrl = featuredImage.startsWith("http") ? featuredImage : `${config.baseUrl}${featuredImage}`;
33308
+ itemXml += `
33309
+ <media:thumbnail url="${this.escapeXml(absoluteImageUrl)}" />`;
33310
+ }
33311
+ itemXml += `
33278
33312
  </item>`;
33313
+ return itemXml;
33279
33314
  }).join(`
33280
33315
  `);
33281
- const rssContent = `<?xml version="1.0" encoding="UTF-8"?>
33282
- <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
33283
- <channel>
33316
+ let channelXml = ` <channel>
33284
33317
  <title><![CDATA[${config.title}]]></title>
33285
- <description><![CDATA[${config.description}]]></description>
33286
33318
  <link>${config.baseUrl}</link>
33287
- <atom:link href="${config.baseUrl}/feed.xml" rel="self" type="application/rss+xml" />
33288
- <lastBuildDate>${this.formatRSSDate(this.getPacificDate(new Date).toISOString())}</lastBuildDate>
33319
+ <description><![CDATA[${config.description}]]></description>`;
33320
+ const language = config.rssLanguage || "en-US";
33321
+ channelXml += `
33322
+ <language>${language}</language>`;
33323
+ if (config.authorEmail && config.authorName) {
33324
+ channelXml += `
33325
+ <managingEditor>${config.authorEmail} (${config.authorName})</managingEditor>`;
33326
+ } else if (config.authorEmail) {
33327
+ channelXml += `
33328
+ <managingEditor>${config.authorEmail}</managingEditor>`;
33329
+ }
33330
+ if (config.webMaster) {
33331
+ channelXml += `
33332
+ <webMaster>${config.webMaster}</webMaster>`;
33333
+ }
33334
+ if (config.copyright) {
33335
+ channelXml += `
33336
+ <copyright><![CDATA[${config.copyright}]]></copyright>`;
33337
+ }
33338
+ channelXml += `
33339
+ <pubDate>${this.formatRSSDate(latestPostDate)}</pubDate>
33340
+ <lastBuildDate>${lastBuildDate}</lastBuildDate>
33341
+ <atom:link href="${config.baseUrl}/feed.xml" rel="self" type="application/rss+xml" />`;
33342
+ const rssContent = `<?xml version="1.0" encoding="UTF-8"?>
33343
+ <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/">
33344
+ ${channelXml}
33289
33345
  ${rssItems}
33290
33346
  </channel>
33291
33347
  </rss>`;
@@ -33476,7 +33532,12 @@ async function handleGenerateCommand(options2, deps = defaultDeps2) {
33476
33532
  deps.logger.log(`- Output directory: ${outputDir}`);
33477
33533
  deps.logger.log(`- Templates directory: ${templatesDir}`);
33478
33534
  const config = await deps.loadConfig(configPath);
33479
- const generator = deps.createGenerator({ contentDir, outputDir, templatesDir, config });
33535
+ const generator = deps.createGenerator({
33536
+ contentDir,
33537
+ outputDir,
33538
+ templatesDir,
33539
+ config
33540
+ });
33480
33541
  await generator.initialize();
33481
33542
  await generator.generate();
33482
33543
  deps.logger.log("Site generation completed successfully!");
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.3",
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",