bunki 0.16.0 → 0.16.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/README.md CHANGED
@@ -67,13 +67,31 @@ export default (): SiteConfig => ({
67
67
 
68
68
  ## Content & Frontmatter
69
69
 
70
- Create Markdown files in `content/YYYY/` (e.g., `content/2025/my-post.md`):
70
+ Create Markdown files in `content/YYYY/` using either pattern:
71
+
72
+ **Option 1: Single file** (traditional)
73
+ ```
74
+ content/2025/my-post.md
75
+ ```
76
+
77
+ **Option 2: Directory with README** (Obsidian-friendly)
78
+ ```
79
+ content/2025/my-post/README.md
80
+ content/2025/my-post/image.jpg
81
+ ```
82
+
83
+ Both patterns generate the same output: `dist/2025/my-post/index.html`
84
+
85
+ > [!WARNING]
86
+ > You cannot have both patterns for the same slug. Bunki will throw a validation error if both `content/2025/my-post.md` AND `content/2025/my-post/README.md` exist.
87
+
88
+ Example frontmatter:
71
89
 
72
90
  ```markdown
73
91
  ---
74
92
  title: "Post Title"
75
93
  date: 2025-01-15T09:00:00-07:00
76
- tags: [web, performance]
94
+ tags: [web-development, performance-optimization]
77
95
  excerpt: "Optional summary for listings"
78
96
  ---
79
97
 
@@ -89,11 +107,21 @@ Your content here with **markdown** support.
89
107
  </video>
90
108
  ```
91
109
 
110
+ ### Tag Format
111
+
112
+ > [!IMPORTANT]
113
+ > Tags must use hyphens instead of spaces: `web-development` NOT `"web development"`
114
+
115
+ Tags with spaces will fail validation. Use hyphenated slugs:
116
+ - ✅ `tags: [new-york-city, travel, family-friendly]`
117
+ - ❌ `tags: ["new york city", "travel", "family friendly"]`
118
+
92
119
  Optional: Define tag descriptions in `src/tags.toml`:
93
120
 
94
121
  ```toml
95
- performance = "Performance optimization and speed"
96
- web = "Web development and technology"
122
+ performance-optimization = "Performance optimization and speed"
123
+ web-development = "Web development and technology"
124
+ new-york-city = "New York City travel guides"
97
125
  ```
98
126
 
99
127
  ### Business Location Data
package/dist/cli.js CHANGED
@@ -28370,13 +28370,16 @@ function registerCssCommand(program2) {
28370
28370
  }
28371
28371
 
28372
28372
  // src/cli/commands/generate.ts
28373
- import path6 from "path";
28373
+ import path7 from "path";
28374
28374
 
28375
28375
  // src/site-generator.ts
28376
28376
  var import_nunjucks = __toESM(require_nunjucks(), 1);
28377
28377
  var import_slugify = __toESM(require_slugify(), 1);
28378
28378
  var {Glob: Glob2 } = globalThis.Bun;
28379
28379
  import fs2 from "fs";
28380
+ import path6 from "path";
28381
+
28382
+ // src/parser.ts
28380
28383
  import path5 from "path";
28381
28384
 
28382
28385
  // src/utils/file-utils.ts
@@ -28423,7 +28426,12 @@ async function readFileAsText(filePath) {
28423
28426
  }
28424
28427
  }
28425
28428
  function getBaseFilename(filePath, extension = ".md") {
28426
- return path4.basename(filePath, extension);
28429
+ const basename = path4.basename(filePath, extension);
28430
+ if (basename.toLowerCase() === "readme") {
28431
+ const dir = path4.dirname(filePath);
28432
+ return path4.basename(dir);
28433
+ }
28434
+ return basename;
28427
28435
  }
28428
28436
  async function createDir(dirPath) {
28429
28437
  try {
@@ -33214,6 +33222,20 @@ async function parseMarkdownFile(filePath, cdnConfig) {
33214
33222
  };
33215
33223
  }
33216
33224
  }
33225
+ if (data.tags && Array.isArray(data.tags)) {
33226
+ const tagsWithSpaces = data.tags.filter((tag) => tag.includes(" "));
33227
+ if (tagsWithSpaces.length > 0) {
33228
+ return {
33229
+ post: null,
33230
+ error: {
33231
+ file: filePath,
33232
+ type: "validation",
33233
+ message: `Tags must not contain spaces. Found: ${tagsWithSpaces.map((t) => `"${t}"`).join(", ")}`,
33234
+ suggestion: `Use hyphens instead of spaces. Example: "new-york-city" instead of "new york city"`
33235
+ }
33236
+ };
33237
+ }
33238
+ }
33217
33239
  let slug = getBaseFilename(filePath);
33218
33240
  const sanitizedHtml = convertMarkdownToHtml(content, cdnConfig);
33219
33241
  const pacificDate = toPacificTime(data.date);
@@ -33277,14 +33299,55 @@ async function parseMarkdownFile(filePath, cdnConfig) {
33277
33299
  }
33278
33300
 
33279
33301
  // src/parser.ts
33302
+ function detectFileConflicts(files) {
33303
+ const errors = [];
33304
+ const slugMap = new Map;
33305
+ for (const filePath of files) {
33306
+ const slug = getBaseFilename(filePath);
33307
+ const dir = path5.dirname(filePath);
33308
+ const year = path5.basename(dir);
33309
+ const key = `${year}/${slug}`;
33310
+ if (!slugMap.has(key)) {
33311
+ slugMap.set(key, []);
33312
+ }
33313
+ slugMap.get(key).push(filePath);
33314
+ }
33315
+ for (const [key, paths] of slugMap.entries()) {
33316
+ if (paths.length > 1) {
33317
+ errors.push({
33318
+ file: paths[0],
33319
+ type: "validation",
33320
+ message: `Conflicting files for '${key}': ${paths.map((p) => path5.relative(process.cwd(), p)).join(" AND ")}`,
33321
+ suggestion: `Remove one of the files. Keep either the .md file OR the /README.md file, not both.`
33322
+ });
33323
+ }
33324
+ }
33325
+ return errors;
33326
+ }
33280
33327
  async function parseMarkdownDirectory(contentDir, strictMode = false, cdnConfig) {
33281
33328
  try {
33282
33329
  const markdownFiles = await findFilesByPattern("**/*.md", contentDir, true);
33283
33330
  console.log(`Found ${markdownFiles.length} markdown files`);
33331
+ const conflictErrors = detectFileConflicts(markdownFiles);
33332
+ if (conflictErrors.length > 0) {
33333
+ console.error(`
33334
+ \u26A0\uFE0F Found ${conflictErrors.length} file conflict(s):
33335
+ `);
33336
+ conflictErrors.forEach((e) => {
33337
+ console.error(` \u274C ${e.message}`);
33338
+ if (e.suggestion) {
33339
+ console.error(` \uD83D\uDCA1 ${e.suggestion}`);
33340
+ }
33341
+ });
33342
+ console.error("");
33343
+ if (strictMode) {
33344
+ throw new Error(`File conflicts detected. Fix conflicts before building.`);
33345
+ }
33346
+ }
33284
33347
  const resultsPromises = markdownFiles.map((filePath) => parseMarkdownFile(filePath, cdnConfig));
33285
33348
  const results = await Promise.all(resultsPromises);
33286
33349
  const posts = [];
33287
- const errors = [];
33350
+ const errors = [...conflictErrors];
33288
33351
  for (const result of results) {
33289
33352
  if (result.post) {
33290
33353
  posts.push(result.post);
@@ -33618,7 +33681,7 @@ class SiteGenerator {
33618
33681
  setNoFollowExceptions(this.options.config.noFollowExceptions);
33619
33682
  }
33620
33683
  let tagDescriptions = {};
33621
- const tagsTomlPath = path5.join(process.cwd(), "src", "tags.toml");
33684
+ const tagsTomlPath = path6.join(process.cwd(), "src", "tags.toml");
33622
33685
  const tagsTomlFile = Bun.file(tagsTomlPath);
33623
33686
  if (await tagsTomlFile.exists()) {
33624
33687
  try {
@@ -33686,7 +33749,7 @@ class SiteGenerator {
33686
33749
  const notFoundHtml = import_nunjucks.default.render("404.njk", {
33687
33750
  site: this.options.config
33688
33751
  });
33689
- await Bun.write(path5.join(this.options.outputDir, "404.html"), notFoundHtml);
33752
+ await Bun.write(path6.join(this.options.outputDir, "404.html"), notFoundHtml);
33690
33753
  console.log("Generated 404.html");
33691
33754
  } catch (error) {
33692
33755
  if (error instanceof Error && error.message.includes("404.njk")) {
@@ -33698,7 +33761,7 @@ class SiteGenerator {
33698
33761
  }
33699
33762
  async generateYearArchives() {
33700
33763
  for (const [year, yearPosts] of Object.entries(this.site.postsByYear)) {
33701
- const yearDir = path5.join(this.options.outputDir, year);
33764
+ const yearDir = path6.join(this.options.outputDir, year);
33702
33765
  await ensureDir(yearDir);
33703
33766
  const pageSize = 10;
33704
33767
  const totalPages = Math.ceil(yearPosts.length / pageSize);
@@ -33737,11 +33800,11 @@ class SiteGenerator {
33737
33800
  jsonLd
33738
33801
  });
33739
33802
  if (page === 1) {
33740
- await Bun.write(path5.join(yearDir, "index.html"), yearPageHtml);
33803
+ await Bun.write(path6.join(yearDir, "index.html"), yearPageHtml);
33741
33804
  } else {
33742
- const pageDir = path5.join(yearDir, "page", page.toString());
33805
+ const pageDir = path6.join(yearDir, "page", page.toString());
33743
33806
  await ensureDir(pageDir);
33744
- await Bun.write(path5.join(pageDir, "index.html"), yearPageHtml);
33807
+ await Bun.write(path6.join(pageDir, "index.html"), yearPageHtml);
33745
33808
  }
33746
33809
  }
33747
33810
  }
@@ -33771,18 +33834,18 @@ class SiteGenerator {
33771
33834
  noindex: page > 2
33772
33835
  });
33773
33836
  if (page === 1) {
33774
- await Bun.write(path5.join(this.options.outputDir, "index.html"), pageHtml);
33837
+ await Bun.write(path6.join(this.options.outputDir, "index.html"), pageHtml);
33775
33838
  } else {
33776
- const pageDir = path5.join(this.options.outputDir, "page", page.toString());
33839
+ const pageDir = path6.join(this.options.outputDir, "page", page.toString());
33777
33840
  await ensureDir(pageDir);
33778
- await Bun.write(path5.join(pageDir, "index.html"), pageHtml);
33841
+ await Bun.write(path6.join(pageDir, "index.html"), pageHtml);
33779
33842
  }
33780
33843
  }
33781
33844
  }
33782
33845
  async generatePostPages() {
33783
33846
  for (const post of this.site.posts) {
33784
33847
  const postPath = post.url.substring(1);
33785
- const postDir = path5.join(this.options.outputDir, postPath);
33848
+ const postDir = path6.join(this.options.outputDir, postPath);
33786
33849
  await ensureDir(postDir);
33787
33850
  const imageUrl = extractFirstImageUrl(post.html, this.options.config.baseUrl);
33788
33851
  const schemas = generatePostPageSchemas({
@@ -33797,19 +33860,19 @@ class SiteGenerator {
33797
33860
  post,
33798
33861
  jsonLd
33799
33862
  });
33800
- await Bun.write(path5.join(postDir, "index.html"), postHtml);
33863
+ await Bun.write(path6.join(postDir, "index.html"), postHtml);
33801
33864
  }
33802
33865
  }
33803
33866
  async generateTagPages() {
33804
- const tagsDir = path5.join(this.options.outputDir, "tags");
33867
+ const tagsDir = path6.join(this.options.outputDir, "tags");
33805
33868
  await ensureDir(tagsDir);
33806
33869
  const tagIndexHtml = import_nunjucks.default.render("tags.njk", {
33807
33870
  site: this.options.config,
33808
33871
  tags: this.getSortedTags()
33809
33872
  });
33810
- await Bun.write(path5.join(tagsDir, "index.html"), tagIndexHtml);
33873
+ await Bun.write(path6.join(tagsDir, "index.html"), tagIndexHtml);
33811
33874
  for (const [tagName, tagData] of Object.entries(this.site.tags)) {
33812
- const tagDir = path5.join(tagsDir, tagData.slug);
33875
+ const tagDir = path6.join(tagsDir, tagData.slug);
33813
33876
  await ensureDir(tagDir);
33814
33877
  const pageSize = 10;
33815
33878
  const totalPages = Math.ceil(tagData.posts.length / pageSize);
@@ -33852,24 +33915,24 @@ class SiteGenerator {
33852
33915
  jsonLd
33853
33916
  });
33854
33917
  if (page === 1) {
33855
- await Bun.write(path5.join(tagDir, "index.html"), tagPageHtml);
33918
+ await Bun.write(path6.join(tagDir, "index.html"), tagPageHtml);
33856
33919
  } else {
33857
- const pageDir = path5.join(tagDir, "page", page.toString());
33920
+ const pageDir = path6.join(tagDir, "page", page.toString());
33858
33921
  await ensureDir(pageDir);
33859
- await Bun.write(path5.join(pageDir, "index.html"), tagPageHtml);
33922
+ await Bun.write(path6.join(pageDir, "index.html"), tagPageHtml);
33860
33923
  }
33861
33924
  }
33862
33925
  }
33863
33926
  }
33864
33927
  async generateMapPage() {
33865
33928
  try {
33866
- const mapDir = path5.join(this.options.outputDir, "map");
33929
+ const mapDir = path6.join(this.options.outputDir, "map");
33867
33930
  await ensureDir(mapDir);
33868
33931
  const mapHtml = import_nunjucks.default.render("map.njk", {
33869
33932
  site: this.options.config,
33870
33933
  posts: this.site.posts
33871
33934
  });
33872
- await Bun.write(path5.join(mapDir, "index.html"), mapHtml);
33935
+ await Bun.write(path6.join(mapDir, "index.html"), mapHtml);
33873
33936
  console.log("Generated map page");
33874
33937
  } catch (error) {
33875
33938
  if (error instanceof Error && error.message.includes("map.njk")) {
@@ -33899,7 +33962,7 @@ class SiteGenerator {
33899
33962
  }
33900
33963
  }
33901
33964
  async fallbackCSSGeneration(cssConfig) {
33902
- const cssFilePath = path5.resolve(process.cwd(), cssConfig.input);
33965
+ const cssFilePath = path6.resolve(process.cwd(), cssConfig.input);
33903
33966
  const cssFile = Bun.file(cssFilePath);
33904
33967
  if (!await cssFile.exists()) {
33905
33968
  console.warn(`CSS input file not found: ${cssFilePath}`);
@@ -33907,8 +33970,8 @@ class SiteGenerator {
33907
33970
  }
33908
33971
  try {
33909
33972
  const cssContent = await cssFile.text();
33910
- const outputPath = path5.resolve(this.options.outputDir, cssConfig.output);
33911
- const outputDir = path5.dirname(outputPath);
33973
+ const outputPath = path6.resolve(this.options.outputDir, cssConfig.output);
33974
+ const outputDir = path6.dirname(outputPath);
33912
33975
  await ensureDir(outputDir);
33913
33976
  await Bun.write(outputPath, cssContent);
33914
33977
  console.log("\u2705 CSS file copied successfully (fallback mode)");
@@ -33917,8 +33980,8 @@ class SiteGenerator {
33917
33980
  }
33918
33981
  }
33919
33982
  async copyStaticAssets() {
33920
- const assetsDir = path5.join(this.options.templatesDir, "assets");
33921
- const publicDir = path5.join(process.cwd(), "public");
33983
+ const assetsDir = path6.join(this.options.templatesDir, "assets");
33984
+ const publicDir = path6.join(process.cwd(), "public");
33922
33985
  async function dirExists(p) {
33923
33986
  try {
33924
33987
  const stat = await fs2.promises.stat(p);
@@ -33930,15 +33993,15 @@ class SiteGenerator {
33930
33993
  const assetsDirFile = Bun.file(assetsDir);
33931
33994
  if (await assetsDirFile.exists() && await dirExists(assetsDir)) {
33932
33995
  const assetGlob = new Glob2("**/*.*");
33933
- const assetsOutputDir = path5.join(this.options.outputDir, "assets");
33996
+ const assetsOutputDir = path6.join(this.options.outputDir, "assets");
33934
33997
  await ensureDir(assetsOutputDir);
33935
33998
  for await (const file of assetGlob.scan({
33936
33999
  cwd: assetsDir,
33937
34000
  absolute: true
33938
34001
  })) {
33939
- const relativePath = path5.relative(assetsDir, file);
33940
- const targetPath = path5.join(assetsOutputDir, relativePath);
33941
- const targetDir = path5.dirname(targetPath);
34002
+ const relativePath = path6.relative(assetsDir, file);
34003
+ const targetPath = path6.join(assetsOutputDir, relativePath);
34004
+ const targetDir = path6.dirname(targetPath);
33942
34005
  await ensureDir(targetDir);
33943
34006
  await copyFile(file, targetPath);
33944
34007
  }
@@ -33949,9 +34012,9 @@ class SiteGenerator {
33949
34012
  withFileTypes: true
33950
34013
  });
33951
34014
  for (const entry of entries) {
33952
- const srcPath = path5.join(srcDir, entry.name);
33953
- const relativePath = path5.relative(publicDir, srcPath);
33954
- const destPath = path5.join(this.options.outputDir, relativePath);
34015
+ const srcPath = path6.join(srcDir, entry.name);
34016
+ const relativePath = path6.relative(publicDir, srcPath);
34017
+ const destPath = path6.join(this.options.outputDir, relativePath);
33955
34018
  if (!relativePath)
33956
34019
  continue;
33957
34020
  if (entry.isDirectory()) {
@@ -33960,7 +34023,7 @@ class SiteGenerator {
33960
34023
  } else if (entry.isFile()) {
33961
34024
  const targetFile = Bun.file(destPath);
33962
34025
  if (!await targetFile.exists()) {
33963
- const targetDir = path5.dirname(destPath);
34026
+ const targetDir = path6.dirname(destPath);
33964
34027
  await ensureDir(targetDir);
33965
34028
  await copyFile(srcPath, destPath);
33966
34029
  }
@@ -34060,7 +34123,7 @@ ${channelXml}
34060
34123
  ${rssItems}
34061
34124
  </channel>
34062
34125
  </rss>`;
34063
- await Bun.write(path5.join(this.options.outputDir, "feed.xml"), rssContent);
34126
+ await Bun.write(path6.join(this.options.outputDir, "feed.xml"), rssContent);
34064
34127
  }
34065
34128
  async generateSitemap() {
34066
34129
  const currentDate = toPacificTime(new Date).toISOString();
@@ -34179,7 +34242,7 @@ ${rssItems}
34179
34242
  }
34180
34243
  }
34181
34244
  sitemapContent += `</urlset>`;
34182
- await Bun.write(path5.join(this.options.outputDir, "sitemap.xml"), sitemapContent);
34245
+ await Bun.write(path6.join(this.options.outputDir, "sitemap.xml"), sitemapContent);
34183
34246
  console.log("Generated sitemap.xml");
34184
34247
  const urlCount = this.site.posts.length + Object.keys(this.site.tags).length + 10;
34185
34248
  const sitemapSize = sitemapContent.length;
@@ -34199,7 +34262,7 @@ ${rssItems}
34199
34262
  </sitemap>
34200
34263
  `;
34201
34264
  sitemapIndexContent += `</sitemapindex>`;
34202
- await Bun.write(path5.join(this.options.outputDir, "sitemap_index.xml"), sitemapIndexContent);
34265
+ await Bun.write(path6.join(this.options.outputDir, "sitemap_index.xml"), sitemapIndexContent);
34203
34266
  console.log("Generated sitemap_index.xml");
34204
34267
  }
34205
34268
  async generateRobotsTxt() {
@@ -34221,7 +34284,7 @@ Sitemap: ${config.baseUrl}/sitemap.xml
34221
34284
  # Disallow: /admin/
34222
34285
  # Disallow: /api/
34223
34286
  `;
34224
- await Bun.write(path5.join(this.options.outputDir, "robots.txt"), robotsTxtContent);
34287
+ await Bun.write(path6.join(this.options.outputDir, "robots.txt"), robotsTxtContent);
34225
34288
  console.log("Generated robots.txt");
34226
34289
  }
34227
34290
  }
@@ -34235,10 +34298,10 @@ var defaultDeps2 = {
34235
34298
  };
34236
34299
  async function handleGenerateCommand(options2, deps = defaultDeps2) {
34237
34300
  try {
34238
- const configPath = path6.resolve(options2.config);
34239
- const contentDir = path6.resolve(options2.content);
34240
- const outputDir = path6.resolve(options2.output);
34241
- const templatesDir = path6.resolve(options2.templates);
34301
+ const configPath = path7.resolve(options2.config);
34302
+ const contentDir = path7.resolve(options2.content);
34303
+ const outputDir = path7.resolve(options2.output);
34304
+ const templatesDir = path7.resolve(options2.templates);
34242
34305
  deps.logger.log("Generating site with:");
34243
34306
  deps.logger.log(`- Config file: ${configPath}`);
34244
34307
  deps.logger.log(`- Content directory: ${contentDir}`);
@@ -34266,11 +34329,11 @@ function registerGenerateCommand(program2) {
34266
34329
  }
34267
34330
 
34268
34331
  // src/utils/image-uploader.ts
34269
- import path8 from "path";
34332
+ import path9 from "path";
34270
34333
 
34271
34334
  // src/utils/s3-uploader.ts
34272
34335
  var {S3Client } = globalThis.Bun;
34273
- import path7 from "path";
34336
+ import path8 from "path";
34274
34337
 
34275
34338
  class S3Uploader {
34276
34339
  s3Config;
@@ -34393,8 +34456,8 @@ class S3Uploader {
34393
34456
  let failedCount = 0;
34394
34457
  const uploadTasks = imageFiles.map((imageFile) => async () => {
34395
34458
  try {
34396
- const imagePath = path7.join(imagesDir, imageFile);
34397
- const filename = path7.basename(imagePath);
34459
+ const imagePath = path8.join(imagesDir, imageFile);
34460
+ const filename = path8.basename(imagePath);
34398
34461
  const file = Bun.file(imagePath);
34399
34462
  const contentType = file.type;
34400
34463
  if (process.env.BUNKI_DRY_RUN === "true") {} else {
@@ -34428,10 +34491,10 @@ function createUploader(config) {
34428
34491
  }
34429
34492
 
34430
34493
  // src/utils/image-uploader.ts
34431
- var DEFAULT_IMAGES_DIR = path8.join(process.cwd(), "assets");
34494
+ var DEFAULT_IMAGES_DIR = path9.join(process.cwd(), "assets");
34432
34495
  async function uploadImages(options2 = {}) {
34433
34496
  try {
34434
- const imagesDir = path8.resolve(options2.images || DEFAULT_IMAGES_DIR);
34497
+ const imagesDir = path9.resolve(options2.images || DEFAULT_IMAGES_DIR);
34435
34498
  if (!await fileExists(imagesDir)) {
34436
34499
  console.log(`Creating images directory at ${imagesDir}...`);
34437
34500
  await ensureDir(imagesDir);
@@ -34468,7 +34531,7 @@ async function uploadImages(options2 = {}) {
34468
34531
  const uploader = createUploader(s3Config);
34469
34532
  const imageUrlMap = await uploader.uploadImages(imagesDir, options2.minYear);
34470
34533
  if (options2.outputJson) {
34471
- const outputFile = path8.resolve(options2.outputJson);
34534
+ const outputFile = path9.resolve(options2.outputJson);
34472
34535
  await Bun.write(outputFile, JSON.stringify(imageUrlMap, null, 2));
34473
34536
  console.log(`Image URL mapping saved to ${outputFile}`);
34474
34537
  }
@@ -34514,7 +34577,7 @@ function registerImagesPushCommand(program2) {
34514
34577
  }
34515
34578
 
34516
34579
  // src/cli/commands/init.ts
34517
- import path9 from "path";
34580
+ import path10 from "path";
34518
34581
  var defaultDependencies = {
34519
34582
  createDefaultConfig,
34520
34583
  ensureDir,
@@ -34524,7 +34587,7 @@ var defaultDependencies = {
34524
34587
  };
34525
34588
  async function handleInitCommand(options2, deps = defaultDependencies) {
34526
34589
  try {
34527
- const configPath = path9.resolve(options2.config);
34590
+ const configPath = path10.resolve(options2.config);
34528
34591
  const configCreated = await deps.createDefaultConfig(configPath);
34529
34592
  if (!configCreated) {
34530
34593
  deps.logger.log(`
@@ -34533,19 +34596,19 @@ Skipped initialization because the config file already exists`);
34533
34596
  }
34534
34597
  deps.logger.log("Creating directory structure...");
34535
34598
  const baseDir = process.cwd();
34536
- const contentDir = path9.join(baseDir, "content");
34537
- const templatesDir = path9.join(baseDir, "templates");
34538
- const stylesDir = path9.join(templatesDir, "styles");
34539
- const publicDir = path9.join(baseDir, "public");
34599
+ const contentDir = path10.join(baseDir, "content");
34600
+ const templatesDir = path10.join(baseDir, "templates");
34601
+ const stylesDir = path10.join(templatesDir, "styles");
34602
+ const publicDir = path10.join(baseDir, "public");
34540
34603
  await deps.ensureDir(contentDir);
34541
34604
  await deps.ensureDir(templatesDir);
34542
34605
  await deps.ensureDir(stylesDir);
34543
34606
  await deps.ensureDir(publicDir);
34544
34607
  for (const [filename, content] of Object.entries(getDefaultTemplates())) {
34545
- await deps.writeFile(path9.join(templatesDir, filename), content);
34608
+ await deps.writeFile(path10.join(templatesDir, filename), content);
34546
34609
  }
34547
- await deps.writeFile(path9.join(stylesDir, "main.css"), getDefaultCss());
34548
- await deps.writeFile(path9.join(contentDir, "welcome.md"), getSamplePost());
34610
+ await deps.writeFile(path10.join(stylesDir, "main.css"), getDefaultCss());
34611
+ await deps.writeFile(path10.join(contentDir, "welcome.md"), getSamplePost());
34549
34612
  deps.logger.log(`
34550
34613
  Initialization complete! Here are the next steps:`);
34551
34614
  deps.logger.log("1. Edit bunki.config.ts to configure your site");
@@ -35037,7 +35100,7 @@ function hello() {
35037
35100
  }
35038
35101
 
35039
35102
  // src/cli/commands/new-post.ts
35040
- import path10 from "path";
35103
+ import path11 from "path";
35041
35104
  var defaultDeps4 = {
35042
35105
  writeFile: (filePath, data) => Bun.write(filePath, data),
35043
35106
  now: () => new Date,
@@ -35061,7 +35124,7 @@ async function handleNewCommand(title, options2, deps = defaultDeps4) {
35061
35124
  ` + `# ${title}
35062
35125
 
35063
35126
  `;
35064
- const filePath = path10.join(DEFAULT_CONTENT_DIR, `${slug}.md`);
35127
+ const filePath = path11.join(DEFAULT_CONTENT_DIR, `${slug}.md`);
35065
35128
  await deps.writeFile(filePath, frontmatter);
35066
35129
  deps.logger.log(`Created new post: ${filePath}`);
35067
35130
  return filePath;
@@ -35078,11 +35141,11 @@ function registerNewCommand(program2) {
35078
35141
  }
35079
35142
 
35080
35143
  // src/cli/commands/serve.ts
35081
- import path12 from "path";
35144
+ import path13 from "path";
35082
35145
 
35083
35146
  // src/server.ts
35084
35147
  import fs3 from "fs";
35085
- import path11 from "path";
35148
+ import path12 from "path";
35086
35149
  async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
35087
35150
  try {
35088
35151
  const stats = await fs3.promises.stat(outputDir);
@@ -35117,18 +35180,18 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
35117
35180
  let filePath = "";
35118
35181
  if (homePaginationMatch) {
35119
35182
  const pageNumber = homePaginationMatch[1];
35120
- filePath = path11.join(outputDir, "page", pageNumber, "index.html");
35183
+ filePath = path12.join(outputDir, "page", pageNumber, "index.html");
35121
35184
  } else if (tagPaginationMatch) {
35122
35185
  const tagSlug = tagPaginationMatch[1];
35123
35186
  const pageNumber = tagPaginationMatch[2];
35124
- filePath = path11.join(outputDir, "tags", tagSlug, "page", pageNumber, "index.html");
35187
+ filePath = path12.join(outputDir, "tags", tagSlug, "page", pageNumber, "index.html");
35125
35188
  } else if (yearPaginationMatch) {
35126
35189
  const year = yearPaginationMatch[1];
35127
35190
  const pageNumber = yearPaginationMatch[2];
35128
- filePath = path11.join(outputDir, year, "page", pageNumber, "index.html");
35191
+ filePath = path12.join(outputDir, year, "page", pageNumber, "index.html");
35129
35192
  } else {
35130
- const directPath = path11.join(outputDir, pathname);
35131
- const withoutSlash = path11.join(outputDir, pathname + ".html");
35193
+ const directPath = path12.join(outputDir, pathname);
35194
+ const withoutSlash = path12.join(outputDir, pathname + ".html");
35132
35195
  const withHtml = pathname.endsWith(".html") ? directPath : withoutSlash;
35133
35196
  const bunFileDirect = Bun.file(directPath);
35134
35197
  const bunFileHtml = Bun.file(withHtml);
@@ -35137,7 +35200,7 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
35137
35200
  } else if (await bunFileHtml.exists()) {
35138
35201
  filePath = withHtml;
35139
35202
  } else {
35140
- const indexPath = path11.join(outputDir, pathname, "index.html");
35203
+ const indexPath = path12.join(outputDir, pathname, "index.html");
35141
35204
  const bunFileIndex = Bun.file(indexPath);
35142
35205
  if (await bunFileIndex.exists()) {
35143
35206
  filePath = indexPath;
@@ -35151,7 +35214,7 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
35151
35214
  }
35152
35215
  }
35153
35216
  console.log(`Serving file: ${filePath}`);
35154
- const extname = path11.extname(filePath);
35217
+ const extname = path12.extname(filePath);
35155
35218
  let contentType = "text/html";
35156
35219
  switch (extname) {
35157
35220
  case ".js":
@@ -35210,7 +35273,7 @@ var defaultDeps5 = {
35210
35273
  };
35211
35274
  async function handleServeCommand(options2, deps = defaultDeps5) {
35212
35275
  try {
35213
- const outputDir = path12.resolve(options2.output);
35276
+ const outputDir = path13.resolve(options2.output);
35214
35277
  const port = parseInt(options2.port, 10);
35215
35278
  await deps.startServer(outputDir, port);
35216
35279
  } catch (error) {