bunki 0.5.3 → 0.6.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
@@ -28185,7 +28185,7 @@ function getDefaultConfig() {
28185
28185
  return base;
28186
28186
  }
28187
28187
  async function createDefaultConfig(configPath = DEFAULT_CONFIG_FILE) {
28188
- if (await configExists()) {
28188
+ if (await configExists(configPath)) {
28189
28189
  console.log(`Config file already exists`);
28190
28190
  return false;
28191
28191
  }
@@ -28243,15 +28243,6 @@ async function processCSS(options2) {
28243
28243
  } catch (error) {
28244
28244
  throw new Error(`CSS input file not found: ${inputPath}`);
28245
28245
  }
28246
- let postcssConfigExists = false;
28247
- try {
28248
- await fs.promises.access(postcssConfigPath);
28249
- postcssConfigExists = true;
28250
- } catch (error) {
28251
- if (verbose) {
28252
- console.log(`PostCSS config not found at ${postcssConfigPath}, will fallback to simple copy`);
28253
- }
28254
- }
28255
28246
  const outputDirPath = path2.dirname(outputPath);
28256
28247
  await fs.promises.mkdir(outputDirPath, { recursive: true });
28257
28248
  if (verbose) {
@@ -28260,71 +28251,33 @@ async function processCSS(options2) {
28260
28251
  console.log(`Output: ${outputPath}`);
28261
28252
  console.log(`Config: ${postcssConfigPath}`);
28262
28253
  }
28263
- if (!postcssConfigExists) {
28264
- await fs.promises.copyFile(inputPath, outputPath);
28265
- if (verbose)
28266
- console.log("Copied CSS without PostCSS config");
28267
- return;
28268
- }
28269
- const runPostCSS = (configPathToUse) => {
28270
- return new Promise((resolve, reject) => {
28271
- const args = ["postcss", inputPath, "-o", outputPath];
28272
- if (configPathToUse && fs.existsSync(configPathToUse)) {
28273
- args.push("--config", configPathToUse);
28274
- }
28275
- const postcss = spawn("bunx", args, {
28276
- stdio: verbose ? "inherit" : ["ignore", "pipe", "pipe"],
28277
- cwd: projectRoot
28254
+ await runPostCSS(inputPath, outputPath, postcssConfigPath, projectRoot, verbose);
28255
+ }
28256
+ function runPostCSS(inputPath, outputPath, configPath, projectRoot, verbose) {
28257
+ return new Promise((resolve, reject) => {
28258
+ const args = ["postcss", inputPath, "-o", outputPath, "--config", configPath];
28259
+ const postcss = spawn("bunx", args, {
28260
+ stdio: verbose ? "inherit" : ["ignore", "pipe", "pipe"],
28261
+ cwd: projectRoot
28262
+ });
28263
+ let errorOutput = "";
28264
+ if (!verbose) {
28265
+ postcss.stderr?.on("data", (data) => {
28266
+ errorOutput += data.toString();
28278
28267
  });
28279
- let errorOutput = "";
28280
- if (!verbose) {
28281
- postcss.stderr?.on("data", (data) => {
28282
- errorOutput += data.toString();
28283
- });
28268
+ }
28269
+ postcss.on("close", (code) => {
28270
+ if (code === 0) {
28271
+ if (verbose)
28272
+ console.log("\u2705 CSS build completed successfully!");
28273
+ return resolve();
28284
28274
  }
28285
- postcss.on("close", async (code) => {
28286
- if (code === 0) {
28287
- if (verbose)
28288
- console.log("\u2705 CSS build completed successfully!");
28289
- return resolve();
28290
- }
28291
- if (/module is not defined in ES module scope/i.test(errorOutput) && configPathToUse.endsWith(".js")) {
28292
- const cjsPath = configPathToUse.replace(/\.js$/, ".cjs");
28293
- try {
28294
- if (!fs.existsSync(cjsPath)) {
28295
- const original = await fs.promises.readFile(configPathToUse, "utf-8");
28296
- await fs.promises.writeFile(cjsPath, original, "utf-8");
28297
- if (verbose) {
28298
- console.log(`Retrying PostCSS with converted CommonJS config at ${cjsPath}`);
28299
- }
28300
- }
28301
- return resolve(runPostCSS(cjsPath));
28302
- } catch (e) {
28303
- if (verbose)
28304
- console.warn("CJS fallback failed, copying CSS.");
28305
- await fs.promises.copyFile(inputPath, outputPath);
28306
- return resolve();
28307
- }
28308
- }
28309
- if (verbose) {
28310
- console.warn(`PostCSS failed (code ${code}). Falling back to simple copy. Error: ${errorOutput.trim()}`);
28311
- }
28312
- try {
28313
- await fs.promises.copyFile(inputPath, outputPath);
28314
- resolve();
28315
- } catch (copyErr) {
28316
- reject(new Error(`CSS build failed with code ${code} and fallback copy also failed: ${copyErr.message}`));
28317
- }
28318
- });
28319
- postcss.on("error", (err) => {
28320
- if (verbose) {
28321
- console.warn(`Failed to start PostCSS process (${err.message}). Falling back to copy.`);
28322
- }
28323
- fs.promises.copyFile(inputPath, outputPath).then(() => resolve()).catch((copyErr) => reject(new Error(`Failed to start PostCSS and fallback copy failed: ${copyErr.message}`)));
28324
- });
28275
+ reject(new Error(`PostCSS failed with exit code ${code}: ${errorOutput.trim()}`));
28325
28276
  });
28326
- };
28327
- await runPostCSS(postcssConfigPath);
28277
+ postcss.on("error", (err) => {
28278
+ reject(new Error(`Failed to start PostCSS: ${err.message}`));
28279
+ });
28280
+ });
28328
28281
  }
28329
28282
  async function watchCSS(options2) {
28330
28283
  const { css, projectRoot, verbose = false } = options2;
@@ -28422,6 +28375,7 @@ import path5 from "path";
28422
28375
  // src/utils/file-utils.ts
28423
28376
  var {Glob } = globalThis.Bun;
28424
28377
  import path4 from "path";
28378
+ import { mkdir } from "fs/promises";
28425
28379
  async function findFilesByPattern(pattern, directory, absolute = true) {
28426
28380
  const glob = new Glob(pattern);
28427
28381
  const files = [];
@@ -28434,8 +28388,20 @@ async function findFilesByPattern(pattern, directory, absolute = true) {
28434
28388
  return files;
28435
28389
  }
28436
28390
  async function fileExists(filePath) {
28437
- const file = Bun.file(filePath);
28438
- return await file.exists();
28391
+ try {
28392
+ const file = Bun.file(filePath);
28393
+ return await file.exists();
28394
+ } catch {
28395
+ return false;
28396
+ }
28397
+ }
28398
+ async function isDirectory(dirPath) {
28399
+ try {
28400
+ const stat = await Bun.file(dirPath).stat();
28401
+ return stat?.isDirectory() ?? false;
28402
+ } catch {
28403
+ return false;
28404
+ }
28439
28405
  }
28440
28406
  async function readFileAsText(filePath) {
28441
28407
  try {
@@ -28452,18 +28418,33 @@ async function readFileAsText(filePath) {
28452
28418
  function getBaseFilename(filePath, extension = ".md") {
28453
28419
  return path4.basename(filePath, extension);
28454
28420
  }
28455
- async function ensureDir(dirPath) {
28421
+ async function createDir(dirPath) {
28456
28422
  try {
28457
- await Bun.write(`${dirPath}/.gitkeep`, "");
28458
- } catch (error) {}
28423
+ await mkdir(dirPath, { recursive: true });
28424
+ } catch (error) {
28425
+ if (await isDirectory(dirPath)) {
28426
+ return;
28427
+ }
28428
+ if (await fileExists(dirPath)) {
28429
+ throw new Error(`Path exists but is not a directory: ${dirPath}`);
28430
+ }
28431
+ console.error(`Error creating directory ${dirPath}:`, error);
28432
+ throw error;
28433
+ }
28434
+ }
28435
+ async function ensureDir(dirPath) {
28436
+ return createDir(dirPath);
28459
28437
  }
28460
28438
  async function copyFile(sourcePath, targetPath) {
28461
28439
  try {
28462
28440
  const sourceFile = Bun.file(sourcePath);
28463
- const content = await sourceFile.arrayBuffer();
28464
- await Bun.write(targetPath, content);
28441
+ if (!await sourceFile.exists()) {
28442
+ throw new Error(`Source file does not exist: ${sourcePath}`);
28443
+ }
28444
+ await Bun.write(targetPath, sourceFile);
28465
28445
  } catch (error) {
28466
28446
  console.error(`Error copying file from ${sourcePath} to ${targetPath}:`, error);
28447
+ throw error;
28467
28448
  }
28468
28449
  }
28469
28450
 
@@ -33263,29 +33244,85 @@ class SiteGenerator {
33263
33244
  console.log("Copied public files to site (including extensionless & dotfiles)");
33264
33245
  }
33265
33246
  }
33247
+ extractFirstImageUrl(html) {
33248
+ const imgRegex = /<img[^>]+src=["']([^"']+)["']/;
33249
+ const match = html.match(imgRegex);
33250
+ return match ? match[1] : null;
33251
+ }
33252
+ escapeXml(text) {
33253
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
33254
+ }
33266
33255
  async generateRSSFeed() {
33267
33256
  const posts = this.site.posts.slice(0, 15);
33268
33257
  const config = this.options.config;
33258
+ const now = this.getPacificDate(new Date);
33259
+ const latestPostDate = posts.length > 0 ? posts[0].date : now.toISOString();
33260
+ const lastBuildDate = this.formatRSSDate(latestPostDate);
33269
33261
  const rssItems = posts.map((post) => {
33270
33262
  const postUrl = `${config.baseUrl}${post.url}`;
33271
33263
  const pubDate = this.formatRSSDate(post.date);
33272
- return ` <item>
33264
+ const featuredImage = this.extractFirstImageUrl(post.html);
33265
+ const categoryTags = post.tags.map((tag) => ` <category>${this.escapeXml(tag)}</category>`).join(`
33266
+ `);
33267
+ let itemXml = ` <item>
33273
33268
  <title><![CDATA[${post.title}]]></title>
33274
33269
  <link>${postUrl}</link>
33275
- <guid>${postUrl}</guid>
33276
- <pubDate>${pubDate}</pubDate>
33277
- <description><![CDATA[${post.excerpt}]]></description>
33270
+ <guid isPermaLink="true">${postUrl}</guid>
33271
+ <pubDate>${pubDate}</pubDate>`;
33272
+ if (config.authorEmail && config.authorName) {
33273
+ itemXml += `
33274
+ <author>${config.authorEmail} (${config.authorName})</author>`;
33275
+ } else if (config.authorEmail) {
33276
+ itemXml += `
33277
+ <author>${config.authorEmail}</author>`;
33278
+ }
33279
+ itemXml += `
33280
+ <description><![CDATA[${post.excerpt}]]></description>`;
33281
+ if (post.tags.length > 0) {
33282
+ itemXml += `
33283
+ ${categoryTags}`;
33284
+ }
33285
+ itemXml += `
33286
+ <content:encoded><![CDATA[${post.html}]]></content:encoded>`;
33287
+ if (featuredImage) {
33288
+ const absoluteImageUrl = featuredImage.startsWith("http") ? featuredImage : `${config.baseUrl}${featuredImage}`;
33289
+ itemXml += `
33290
+ <media:thumbnail url="${this.escapeXml(absoluteImageUrl)}" />`;
33291
+ }
33292
+ itemXml += `
33278
33293
  </item>`;
33294
+ return itemXml;
33279
33295
  }).join(`
33280
33296
  `);
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>
33297
+ let channelXml = ` <channel>
33284
33298
  <title><![CDATA[${config.title}]]></title>
33285
- <description><![CDATA[${config.description}]]></description>
33286
33299
  <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>
33300
+ <description><![CDATA[${config.description}]]></description>`;
33301
+ const language = config.rssLanguage || "en-US";
33302
+ channelXml += `
33303
+ <language>${language}</language>`;
33304
+ if (config.authorEmail && config.authorName) {
33305
+ channelXml += `
33306
+ <managingEditor>${config.authorEmail} (${config.authorName})</managingEditor>`;
33307
+ } else if (config.authorEmail) {
33308
+ channelXml += `
33309
+ <managingEditor>${config.authorEmail}</managingEditor>`;
33310
+ }
33311
+ if (config.webMaster) {
33312
+ channelXml += `
33313
+ <webMaster>${config.webMaster}</webMaster>`;
33314
+ }
33315
+ if (config.copyright) {
33316
+ channelXml += `
33317
+ <copyright><![CDATA[${config.copyright}]]></copyright>`;
33318
+ }
33319
+ channelXml += `
33320
+ <pubDate>${this.formatRSSDate(latestPostDate)}</pubDate>
33321
+ <lastBuildDate>${lastBuildDate}</lastBuildDate>
33322
+ <atom:link href="${config.baseUrl}/feed.xml" rel="self" type="application/rss+xml" />`;
33323
+ const rssContent = `<?xml version="1.0" encoding="UTF-8"?>
33324
+ <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/">
33325
+ ${channelXml}
33289
33326
  ${rssItems}
33290
33327
  </channel>
33291
33328
  </rss>`;
@@ -33476,7 +33513,12 @@ async function handleGenerateCommand(options2, deps = defaultDeps2) {
33476
33513
  deps.logger.log(`- Output directory: ${outputDir}`);
33477
33514
  deps.logger.log(`- Templates directory: ${templatesDir}`);
33478
33515
  const config = await deps.loadConfig(configPath);
33479
- const generator = deps.createGenerator({ contentDir, outputDir, templatesDir, config });
33516
+ const generator = deps.createGenerator({
33517
+ contentDir,
33518
+ outputDir,
33519
+ templatesDir,
33520
+ config
33521
+ });
33480
33522
  await generator.initialize();
33481
33523
  await generator.generate();
33482
33524
  deps.logger.log("Site generation completed successfully!");
@@ -34191,56 +34233,56 @@ function getDefaultCss() {
34191
34233
  }
34192
34234
  function getSamplePost() {
34193
34235
  return `---
34194
- title: Welcome to Bunki
34195
- date: ${new Date().toISOString()}
34196
- tags: [getting-started, bunki]
34197
- ---
34236
+ title: Welcome to Bunki
34237
+ date: ${new Date().toISOString()}
34238
+ tags: [getting-started, bunki]
34239
+ ---
34198
34240
 
34199
- # Welcome to Your New Bunki Site
34241
+ # Welcome to Your New Bunki Site
34200
34242
 
34201
- This is a sample blog post to help you get started with Bunki. You can edit this file or create new markdown files in the \`content\` directory.
34243
+ This is a sample blog post to help you get started with Bunki. You can edit this file or create new markdown files in the \`content\` directory.
34202
34244
 
34203
- ## Features
34245
+ ## Features
34204
34246
 
34205
- - Markdown support with frontmatter
34206
- - Syntax highlighting for code blocks
34207
- - Tag-based organization
34208
- - Pagination for post listings
34209
- - RSS feed generation
34210
- - Sitemap generation
34247
+ - Markdown support with frontmatter
34248
+ - Syntax highlighting for code blocks
34249
+ - Tag-based organization
34250
+ - Pagination for post listings
34251
+ - RSS feed generation
34252
+ - Sitemap generation
34211
34253
 
34212
- ## Adding Content
34254
+ ## Adding Content
34213
34255
 
34214
- Create new markdown files in the \`content\` directory with frontmatter like this:
34256
+ Create new markdown files in the \`content\` directory with frontmatter like this:
34215
34257
 
34216
- \`\`\`markdown
34217
- ---
34218
- title: Your Post Title
34219
- date: 2025-01-01T12:00:00Z
34220
- tags: [tag1, tag2]
34221
- ---
34258
+ \`\`\`markdown
34259
+ ---
34260
+ title: Your Post Title
34261
+ date: 2025-01-01T12:00:00Z
34262
+ tags: [tag1, tag2]
34263
+ ---
34222
34264
 
34223
- Your post content goes here...
34224
- \`\`\`
34265
+ Your post content goes here...
34266
+ \`\`\`
34225
34267
 
34226
- ## Code Highlighting
34268
+ ## Code Highlighting
34227
34269
 
34228
- Bunki supports syntax highlighting for code blocks:
34270
+ Bunki supports syntax highlighting for code blocks:
34229
34271
 
34230
- \`\`\`javascript
34231
- function hello() {
34232
- console.log('Hello, world!');
34233
- }
34234
- \`\`\`
34272
+ \`\`\`javascript
34273
+ function hello() {
34274
+ console.log('Hello, world!');
34275
+ }
34276
+ \`\`\`
34235
34277
 
34236
- ## Next Steps
34278
+ ## Next Steps
34237
34279
 
34238
- 1. Edit the site configuration in \`bunki.config.ts\`
34239
- 2. Create your own templates in the \`templates\` directory
34240
- 3. Add more blog posts in the \`content\` directory
34241
- 4. Run \`bunki generate\` to build your site
34242
- 5. Run \`bunki serve\` to preview your site locally
34243
- `;
34280
+ 1. Edit the site configuration in \`bunki.config.ts\`
34281
+ 2. Create your own templates in the \`templates\` directory
34282
+ 3. Add more blog posts in the \`content\` directory
34283
+ 4. Run \`bunki generate\` to build your site
34284
+ 5. Run \`bunki serve\` to preview your site locally
34285
+ `;
34244
34286
  }
34245
34287
 
34246
34288
  // src/cli/commands/new-post.ts
@@ -34440,6 +34482,8 @@ registerServeCommand(program2);
34440
34482
  registerCssCommand(program2);
34441
34483
  registerImagesPushCommand(program2);
34442
34484
  program2.name("bunki").description("An opinionated static site generator built with Bun").version("0.5.3");
34443
- if (import.meta.url === Bun.main) {
34485
+ var currentFile = import.meta.url.replace("file://", "");
34486
+ var mainFile = Bun.main;
34487
+ if (currentFile === mainFile || currentFile.endsWith(mainFile)) {
34444
34488
  program2.parse(Bun.argv);
34445
34489
  }