bunki 0.15.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 {
@@ -32902,7 +32910,15 @@ core_default.registerLanguage("python", python);
32902
32910
  core_default.registerLanguage("json", json);
32903
32911
  core_default.registerLanguage("swift", swift);
32904
32912
  var noFollowExceptions = new Set;
32905
- function createMarked() {
32913
+ function transformImagePath(relativePath, config) {
32914
+ const match = relativePath.match(/^\.\.\/\.\.\/assets\/(\d{4})\/([^/]+)\/(.+)$/);
32915
+ if (!match)
32916
+ return null;
32917
+ const [, year, slug, filename] = match;
32918
+ const path5 = config.pathPattern.replace("{year}", year).replace("{slug}", slug).replace("{filename}", filename);
32919
+ return `${config.baseUrl}/${path5}`;
32920
+ }
32921
+ function createMarked(cdnConfig) {
32906
32922
  const marked = new B(markedHighlight({
32907
32923
  emptyLangClass: "hljs",
32908
32924
  langPrefix: "hljs language-",
@@ -32951,6 +32967,15 @@ function createMarked() {
32951
32967
  }
32952
32968
  }
32953
32969
  }
32970
+ if (token.type === "image" && cdnConfig?.enabled) {
32971
+ const href = token.href || "";
32972
+ if (href.startsWith("../../assets/")) {
32973
+ const transformed = transformImagePath(href, cdnConfig);
32974
+ if (transformed) {
32975
+ token.href = transformed;
32976
+ }
32977
+ }
32978
+ }
32954
32979
  },
32955
32980
  hooks: {
32956
32981
  preprocess(markdown2) {
@@ -32993,8 +33018,9 @@ function extractExcerpt(content, maxLength = 200) {
32993
33018
  const lastSpace = truncated.lastIndexOf(" ");
32994
33019
  return truncated.substring(0, lastSpace) + "...";
32995
33020
  }
32996
- function convertMarkdownToHtml(markdownContent) {
32997
- const html = marked.parse(markdownContent, { async: false });
33021
+ function convertMarkdownToHtml(markdownContent, cdnConfig) {
33022
+ const markedInstance = cdnConfig ? createMarked(cdnConfig) : marked;
33023
+ const html = markedInstance.parse(markdownContent, { async: false });
32998
33024
  let sanitized = import_sanitize_html.default(html, {
32999
33025
  allowedTags: import_sanitize_html.default.defaults.allowedTags.concat([
33000
33026
  "img",
@@ -33146,7 +33172,7 @@ function validateBusinessLocation(business, filePath) {
33146
33172
  }
33147
33173
  return null;
33148
33174
  }
33149
- async function parseMarkdownFile(filePath) {
33175
+ async function parseMarkdownFile(filePath, cdnConfig) {
33150
33176
  try {
33151
33177
  const fileContent = await readFileAsText(filePath);
33152
33178
  if (fileContent === null) {
@@ -33196,8 +33222,22 @@ async function parseMarkdownFile(filePath) {
33196
33222
  };
33197
33223
  }
33198
33224
  }
33199
- let slug = data.slug || getBaseFilename(filePath);
33200
- const sanitizedHtml = convertMarkdownToHtml(content);
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
+ }
33239
+ let slug = getBaseFilename(filePath);
33240
+ const sanitizedHtml = convertMarkdownToHtml(content, cdnConfig);
33201
33241
  const pacificDate = toPacificTime(data.date);
33202
33242
  const postYear = getPacificYear(data.date);
33203
33243
  const post = {
@@ -33259,14 +33299,55 @@ async function parseMarkdownFile(filePath) {
33259
33299
  }
33260
33300
 
33261
33301
  // src/parser.ts
33262
- async function parseMarkdownDirectory(contentDir, strictMode = false) {
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
+ }
33327
+ async function parseMarkdownDirectory(contentDir, strictMode = false, cdnConfig) {
33263
33328
  try {
33264
33329
  const markdownFiles = await findFilesByPattern("**/*.md", contentDir, true);
33265
33330
  console.log(`Found ${markdownFiles.length} markdown files`);
33266
- const resultsPromises = markdownFiles.map((filePath) => parseMarkdownFile(filePath));
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
+ }
33347
+ const resultsPromises = markdownFiles.map((filePath) => parseMarkdownFile(filePath, cdnConfig));
33267
33348
  const results = await Promise.all(resultsPromises);
33268
33349
  const posts = [];
33269
- const errors = [];
33350
+ const errors = [...conflictErrors];
33270
33351
  for (const result of results) {
33271
33352
  if (result.post) {
33272
33353
  posts.push(result.post);
@@ -33600,7 +33681,7 @@ class SiteGenerator {
33600
33681
  setNoFollowExceptions(this.options.config.noFollowExceptions);
33601
33682
  }
33602
33683
  let tagDescriptions = {};
33603
- const tagsTomlPath = path5.join(process.cwd(), "src", "tags.toml");
33684
+ const tagsTomlPath = path6.join(process.cwd(), "src", "tags.toml");
33604
33685
  const tagsTomlFile = Bun.file(tagsTomlPath);
33605
33686
  if (await tagsTomlFile.exists()) {
33606
33687
  try {
@@ -33611,7 +33692,7 @@ class SiteGenerator {
33611
33692
  }
33612
33693
  }
33613
33694
  const strictMode = this.options.config.strictMode ?? false;
33614
- const posts = await parseMarkdownDirectory(this.options.contentDir, strictMode);
33695
+ const posts = await parseMarkdownDirectory(this.options.contentDir, strictMode, this.options.config.cdn);
33615
33696
  const tags = {};
33616
33697
  posts.forEach((post) => {
33617
33698
  post.tagSlugs = {};
@@ -33668,7 +33749,7 @@ class SiteGenerator {
33668
33749
  const notFoundHtml = import_nunjucks.default.render("404.njk", {
33669
33750
  site: this.options.config
33670
33751
  });
33671
- await Bun.write(path5.join(this.options.outputDir, "404.html"), notFoundHtml);
33752
+ await Bun.write(path6.join(this.options.outputDir, "404.html"), notFoundHtml);
33672
33753
  console.log("Generated 404.html");
33673
33754
  } catch (error) {
33674
33755
  if (error instanceof Error && error.message.includes("404.njk")) {
@@ -33680,7 +33761,7 @@ class SiteGenerator {
33680
33761
  }
33681
33762
  async generateYearArchives() {
33682
33763
  for (const [year, yearPosts] of Object.entries(this.site.postsByYear)) {
33683
- const yearDir = path5.join(this.options.outputDir, year);
33764
+ const yearDir = path6.join(this.options.outputDir, year);
33684
33765
  await ensureDir(yearDir);
33685
33766
  const pageSize = 10;
33686
33767
  const totalPages = Math.ceil(yearPosts.length / pageSize);
@@ -33719,11 +33800,11 @@ class SiteGenerator {
33719
33800
  jsonLd
33720
33801
  });
33721
33802
  if (page === 1) {
33722
- await Bun.write(path5.join(yearDir, "index.html"), yearPageHtml);
33803
+ await Bun.write(path6.join(yearDir, "index.html"), yearPageHtml);
33723
33804
  } else {
33724
- const pageDir = path5.join(yearDir, "page", page.toString());
33805
+ const pageDir = path6.join(yearDir, "page", page.toString());
33725
33806
  await ensureDir(pageDir);
33726
- await Bun.write(path5.join(pageDir, "index.html"), yearPageHtml);
33807
+ await Bun.write(path6.join(pageDir, "index.html"), yearPageHtml);
33727
33808
  }
33728
33809
  }
33729
33810
  }
@@ -33753,18 +33834,18 @@ class SiteGenerator {
33753
33834
  noindex: page > 2
33754
33835
  });
33755
33836
  if (page === 1) {
33756
- await Bun.write(path5.join(this.options.outputDir, "index.html"), pageHtml);
33837
+ await Bun.write(path6.join(this.options.outputDir, "index.html"), pageHtml);
33757
33838
  } else {
33758
- const pageDir = path5.join(this.options.outputDir, "page", page.toString());
33839
+ const pageDir = path6.join(this.options.outputDir, "page", page.toString());
33759
33840
  await ensureDir(pageDir);
33760
- await Bun.write(path5.join(pageDir, "index.html"), pageHtml);
33841
+ await Bun.write(path6.join(pageDir, "index.html"), pageHtml);
33761
33842
  }
33762
33843
  }
33763
33844
  }
33764
33845
  async generatePostPages() {
33765
33846
  for (const post of this.site.posts) {
33766
33847
  const postPath = post.url.substring(1);
33767
- const postDir = path5.join(this.options.outputDir, postPath);
33848
+ const postDir = path6.join(this.options.outputDir, postPath);
33768
33849
  await ensureDir(postDir);
33769
33850
  const imageUrl = extractFirstImageUrl(post.html, this.options.config.baseUrl);
33770
33851
  const schemas = generatePostPageSchemas({
@@ -33779,19 +33860,19 @@ class SiteGenerator {
33779
33860
  post,
33780
33861
  jsonLd
33781
33862
  });
33782
- await Bun.write(path5.join(postDir, "index.html"), postHtml);
33863
+ await Bun.write(path6.join(postDir, "index.html"), postHtml);
33783
33864
  }
33784
33865
  }
33785
33866
  async generateTagPages() {
33786
- const tagsDir = path5.join(this.options.outputDir, "tags");
33867
+ const tagsDir = path6.join(this.options.outputDir, "tags");
33787
33868
  await ensureDir(tagsDir);
33788
33869
  const tagIndexHtml = import_nunjucks.default.render("tags.njk", {
33789
33870
  site: this.options.config,
33790
33871
  tags: this.getSortedTags()
33791
33872
  });
33792
- await Bun.write(path5.join(tagsDir, "index.html"), tagIndexHtml);
33873
+ await Bun.write(path6.join(tagsDir, "index.html"), tagIndexHtml);
33793
33874
  for (const [tagName, tagData] of Object.entries(this.site.tags)) {
33794
- const tagDir = path5.join(tagsDir, tagData.slug);
33875
+ const tagDir = path6.join(tagsDir, tagData.slug);
33795
33876
  await ensureDir(tagDir);
33796
33877
  const pageSize = 10;
33797
33878
  const totalPages = Math.ceil(tagData.posts.length / pageSize);
@@ -33834,24 +33915,24 @@ class SiteGenerator {
33834
33915
  jsonLd
33835
33916
  });
33836
33917
  if (page === 1) {
33837
- await Bun.write(path5.join(tagDir, "index.html"), tagPageHtml);
33918
+ await Bun.write(path6.join(tagDir, "index.html"), tagPageHtml);
33838
33919
  } else {
33839
- const pageDir = path5.join(tagDir, "page", page.toString());
33920
+ const pageDir = path6.join(tagDir, "page", page.toString());
33840
33921
  await ensureDir(pageDir);
33841
- await Bun.write(path5.join(pageDir, "index.html"), tagPageHtml);
33922
+ await Bun.write(path6.join(pageDir, "index.html"), tagPageHtml);
33842
33923
  }
33843
33924
  }
33844
33925
  }
33845
33926
  }
33846
33927
  async generateMapPage() {
33847
33928
  try {
33848
- const mapDir = path5.join(this.options.outputDir, "map");
33929
+ const mapDir = path6.join(this.options.outputDir, "map");
33849
33930
  await ensureDir(mapDir);
33850
33931
  const mapHtml = import_nunjucks.default.render("map.njk", {
33851
33932
  site: this.options.config,
33852
33933
  posts: this.site.posts
33853
33934
  });
33854
- await Bun.write(path5.join(mapDir, "index.html"), mapHtml);
33935
+ await Bun.write(path6.join(mapDir, "index.html"), mapHtml);
33855
33936
  console.log("Generated map page");
33856
33937
  } catch (error) {
33857
33938
  if (error instanceof Error && error.message.includes("map.njk")) {
@@ -33881,7 +33962,7 @@ class SiteGenerator {
33881
33962
  }
33882
33963
  }
33883
33964
  async fallbackCSSGeneration(cssConfig) {
33884
- const cssFilePath = path5.resolve(process.cwd(), cssConfig.input);
33965
+ const cssFilePath = path6.resolve(process.cwd(), cssConfig.input);
33885
33966
  const cssFile = Bun.file(cssFilePath);
33886
33967
  if (!await cssFile.exists()) {
33887
33968
  console.warn(`CSS input file not found: ${cssFilePath}`);
@@ -33889,8 +33970,8 @@ class SiteGenerator {
33889
33970
  }
33890
33971
  try {
33891
33972
  const cssContent = await cssFile.text();
33892
- const outputPath = path5.resolve(this.options.outputDir, cssConfig.output);
33893
- const outputDir = path5.dirname(outputPath);
33973
+ const outputPath = path6.resolve(this.options.outputDir, cssConfig.output);
33974
+ const outputDir = path6.dirname(outputPath);
33894
33975
  await ensureDir(outputDir);
33895
33976
  await Bun.write(outputPath, cssContent);
33896
33977
  console.log("\u2705 CSS file copied successfully (fallback mode)");
@@ -33899,8 +33980,8 @@ class SiteGenerator {
33899
33980
  }
33900
33981
  }
33901
33982
  async copyStaticAssets() {
33902
- const assetsDir = path5.join(this.options.templatesDir, "assets");
33903
- const publicDir = path5.join(process.cwd(), "public");
33983
+ const assetsDir = path6.join(this.options.templatesDir, "assets");
33984
+ const publicDir = path6.join(process.cwd(), "public");
33904
33985
  async function dirExists(p) {
33905
33986
  try {
33906
33987
  const stat = await fs2.promises.stat(p);
@@ -33912,15 +33993,15 @@ class SiteGenerator {
33912
33993
  const assetsDirFile = Bun.file(assetsDir);
33913
33994
  if (await assetsDirFile.exists() && await dirExists(assetsDir)) {
33914
33995
  const assetGlob = new Glob2("**/*.*");
33915
- const assetsOutputDir = path5.join(this.options.outputDir, "assets");
33996
+ const assetsOutputDir = path6.join(this.options.outputDir, "assets");
33916
33997
  await ensureDir(assetsOutputDir);
33917
33998
  for await (const file of assetGlob.scan({
33918
33999
  cwd: assetsDir,
33919
34000
  absolute: true
33920
34001
  })) {
33921
- const relativePath = path5.relative(assetsDir, file);
33922
- const targetPath = path5.join(assetsOutputDir, relativePath);
33923
- 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);
33924
34005
  await ensureDir(targetDir);
33925
34006
  await copyFile(file, targetPath);
33926
34007
  }
@@ -33931,9 +34012,9 @@ class SiteGenerator {
33931
34012
  withFileTypes: true
33932
34013
  });
33933
34014
  for (const entry of entries) {
33934
- const srcPath = path5.join(srcDir, entry.name);
33935
- const relativePath = path5.relative(publicDir, srcPath);
33936
- 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);
33937
34018
  if (!relativePath)
33938
34019
  continue;
33939
34020
  if (entry.isDirectory()) {
@@ -33942,7 +34023,7 @@ class SiteGenerator {
33942
34023
  } else if (entry.isFile()) {
33943
34024
  const targetFile = Bun.file(destPath);
33944
34025
  if (!await targetFile.exists()) {
33945
- const targetDir = path5.dirname(destPath);
34026
+ const targetDir = path6.dirname(destPath);
33946
34027
  await ensureDir(targetDir);
33947
34028
  await copyFile(srcPath, destPath);
33948
34029
  }
@@ -34042,7 +34123,7 @@ ${channelXml}
34042
34123
  ${rssItems}
34043
34124
  </channel>
34044
34125
  </rss>`;
34045
- await Bun.write(path5.join(this.options.outputDir, "feed.xml"), rssContent);
34126
+ await Bun.write(path6.join(this.options.outputDir, "feed.xml"), rssContent);
34046
34127
  }
34047
34128
  async generateSitemap() {
34048
34129
  const currentDate = toPacificTime(new Date).toISOString();
@@ -34161,7 +34242,7 @@ ${rssItems}
34161
34242
  }
34162
34243
  }
34163
34244
  sitemapContent += `</urlset>`;
34164
- await Bun.write(path5.join(this.options.outputDir, "sitemap.xml"), sitemapContent);
34245
+ await Bun.write(path6.join(this.options.outputDir, "sitemap.xml"), sitemapContent);
34165
34246
  console.log("Generated sitemap.xml");
34166
34247
  const urlCount = this.site.posts.length + Object.keys(this.site.tags).length + 10;
34167
34248
  const sitemapSize = sitemapContent.length;
@@ -34181,7 +34262,7 @@ ${rssItems}
34181
34262
  </sitemap>
34182
34263
  `;
34183
34264
  sitemapIndexContent += `</sitemapindex>`;
34184
- 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);
34185
34266
  console.log("Generated sitemap_index.xml");
34186
34267
  }
34187
34268
  async generateRobotsTxt() {
@@ -34203,7 +34284,7 @@ Sitemap: ${config.baseUrl}/sitemap.xml
34203
34284
  # Disallow: /admin/
34204
34285
  # Disallow: /api/
34205
34286
  `;
34206
- await Bun.write(path5.join(this.options.outputDir, "robots.txt"), robotsTxtContent);
34287
+ await Bun.write(path6.join(this.options.outputDir, "robots.txt"), robotsTxtContent);
34207
34288
  console.log("Generated robots.txt");
34208
34289
  }
34209
34290
  }
@@ -34217,10 +34298,10 @@ var defaultDeps2 = {
34217
34298
  };
34218
34299
  async function handleGenerateCommand(options2, deps = defaultDeps2) {
34219
34300
  try {
34220
- const configPath = path6.resolve(options2.config);
34221
- const contentDir = path6.resolve(options2.content);
34222
- const outputDir = path6.resolve(options2.output);
34223
- 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);
34224
34305
  deps.logger.log("Generating site with:");
34225
34306
  deps.logger.log(`- Config file: ${configPath}`);
34226
34307
  deps.logger.log(`- Content directory: ${contentDir}`);
@@ -34248,11 +34329,11 @@ function registerGenerateCommand(program2) {
34248
34329
  }
34249
34330
 
34250
34331
  // src/utils/image-uploader.ts
34251
- import path8 from "path";
34332
+ import path9 from "path";
34252
34333
 
34253
34334
  // src/utils/s3-uploader.ts
34254
34335
  var {S3Client } = globalThis.Bun;
34255
- import path7 from "path";
34336
+ import path8 from "path";
34256
34337
 
34257
34338
  class S3Uploader {
34258
34339
  s3Config;
@@ -34375,8 +34456,8 @@ class S3Uploader {
34375
34456
  let failedCount = 0;
34376
34457
  const uploadTasks = imageFiles.map((imageFile) => async () => {
34377
34458
  try {
34378
- const imagePath = path7.join(imagesDir, imageFile);
34379
- const filename = path7.basename(imagePath);
34459
+ const imagePath = path8.join(imagesDir, imageFile);
34460
+ const filename = path8.basename(imagePath);
34380
34461
  const file = Bun.file(imagePath);
34381
34462
  const contentType = file.type;
34382
34463
  if (process.env.BUNKI_DRY_RUN === "true") {} else {
@@ -34410,10 +34491,10 @@ function createUploader(config) {
34410
34491
  }
34411
34492
 
34412
34493
  // src/utils/image-uploader.ts
34413
- var DEFAULT_IMAGES_DIR = path8.join(process.cwd(), "images");
34494
+ var DEFAULT_IMAGES_DIR = path9.join(process.cwd(), "assets");
34414
34495
  async function uploadImages(options2 = {}) {
34415
34496
  try {
34416
- const imagesDir = path8.resolve(options2.images || DEFAULT_IMAGES_DIR);
34497
+ const imagesDir = path9.resolve(options2.images || DEFAULT_IMAGES_DIR);
34417
34498
  if (!await fileExists(imagesDir)) {
34418
34499
  console.log(`Creating images directory at ${imagesDir}...`);
34419
34500
  await ensureDir(imagesDir);
@@ -34450,7 +34531,7 @@ async function uploadImages(options2 = {}) {
34450
34531
  const uploader = createUploader(s3Config);
34451
34532
  const imageUrlMap = await uploader.uploadImages(imagesDir, options2.minYear);
34452
34533
  if (options2.outputJson) {
34453
- const outputFile = path8.resolve(options2.outputJson);
34534
+ const outputFile = path9.resolve(options2.outputJson);
34454
34535
  await Bun.write(outputFile, JSON.stringify(imageUrlMap, null, 2));
34455
34536
  console.log(`Image URL mapping saved to ${outputFile}`);
34456
34537
  }
@@ -34496,7 +34577,7 @@ function registerImagesPushCommand(program2) {
34496
34577
  }
34497
34578
 
34498
34579
  // src/cli/commands/init.ts
34499
- import path9 from "path";
34580
+ import path10 from "path";
34500
34581
  var defaultDependencies = {
34501
34582
  createDefaultConfig,
34502
34583
  ensureDir,
@@ -34506,7 +34587,7 @@ var defaultDependencies = {
34506
34587
  };
34507
34588
  async function handleInitCommand(options2, deps = defaultDependencies) {
34508
34589
  try {
34509
- const configPath = path9.resolve(options2.config);
34590
+ const configPath = path10.resolve(options2.config);
34510
34591
  const configCreated = await deps.createDefaultConfig(configPath);
34511
34592
  if (!configCreated) {
34512
34593
  deps.logger.log(`
@@ -34515,19 +34596,19 @@ Skipped initialization because the config file already exists`);
34515
34596
  }
34516
34597
  deps.logger.log("Creating directory structure...");
34517
34598
  const baseDir = process.cwd();
34518
- const contentDir = path9.join(baseDir, "content");
34519
- const templatesDir = path9.join(baseDir, "templates");
34520
- const stylesDir = path9.join(templatesDir, "styles");
34521
- 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");
34522
34603
  await deps.ensureDir(contentDir);
34523
34604
  await deps.ensureDir(templatesDir);
34524
34605
  await deps.ensureDir(stylesDir);
34525
34606
  await deps.ensureDir(publicDir);
34526
34607
  for (const [filename, content] of Object.entries(getDefaultTemplates())) {
34527
- await deps.writeFile(path9.join(templatesDir, filename), content);
34608
+ await deps.writeFile(path10.join(templatesDir, filename), content);
34528
34609
  }
34529
- await deps.writeFile(path9.join(stylesDir, "main.css"), getDefaultCss());
34530
- 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());
34531
34612
  deps.logger.log(`
34532
34613
  Initialization complete! Here are the next steps:`);
34533
34614
  deps.logger.log("1. Edit bunki.config.ts to configure your site");
@@ -35019,7 +35100,7 @@ function hello() {
35019
35100
  }
35020
35101
 
35021
35102
  // src/cli/commands/new-post.ts
35022
- import path10 from "path";
35103
+ import path11 from "path";
35023
35104
  var defaultDeps4 = {
35024
35105
  writeFile: (filePath, data) => Bun.write(filePath, data),
35025
35106
  now: () => new Date,
@@ -35043,7 +35124,7 @@ async function handleNewCommand(title, options2, deps = defaultDeps4) {
35043
35124
  ` + `# ${title}
35044
35125
 
35045
35126
  `;
35046
- const filePath = path10.join(DEFAULT_CONTENT_DIR, `${slug}.md`);
35127
+ const filePath = path11.join(DEFAULT_CONTENT_DIR, `${slug}.md`);
35047
35128
  await deps.writeFile(filePath, frontmatter);
35048
35129
  deps.logger.log(`Created new post: ${filePath}`);
35049
35130
  return filePath;
@@ -35060,11 +35141,11 @@ function registerNewCommand(program2) {
35060
35141
  }
35061
35142
 
35062
35143
  // src/cli/commands/serve.ts
35063
- import path12 from "path";
35144
+ import path13 from "path";
35064
35145
 
35065
35146
  // src/server.ts
35066
35147
  import fs3 from "fs";
35067
- import path11 from "path";
35148
+ import path12 from "path";
35068
35149
  async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
35069
35150
  try {
35070
35151
  const stats = await fs3.promises.stat(outputDir);
@@ -35099,18 +35180,18 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
35099
35180
  let filePath = "";
35100
35181
  if (homePaginationMatch) {
35101
35182
  const pageNumber = homePaginationMatch[1];
35102
- filePath = path11.join(outputDir, "page", pageNumber, "index.html");
35183
+ filePath = path12.join(outputDir, "page", pageNumber, "index.html");
35103
35184
  } else if (tagPaginationMatch) {
35104
35185
  const tagSlug = tagPaginationMatch[1];
35105
35186
  const pageNumber = tagPaginationMatch[2];
35106
- filePath = path11.join(outputDir, "tags", tagSlug, "page", pageNumber, "index.html");
35187
+ filePath = path12.join(outputDir, "tags", tagSlug, "page", pageNumber, "index.html");
35107
35188
  } else if (yearPaginationMatch) {
35108
35189
  const year = yearPaginationMatch[1];
35109
35190
  const pageNumber = yearPaginationMatch[2];
35110
- filePath = path11.join(outputDir, year, "page", pageNumber, "index.html");
35191
+ filePath = path12.join(outputDir, year, "page", pageNumber, "index.html");
35111
35192
  } else {
35112
- const directPath = path11.join(outputDir, pathname);
35113
- const withoutSlash = path11.join(outputDir, pathname + ".html");
35193
+ const directPath = path12.join(outputDir, pathname);
35194
+ const withoutSlash = path12.join(outputDir, pathname + ".html");
35114
35195
  const withHtml = pathname.endsWith(".html") ? directPath : withoutSlash;
35115
35196
  const bunFileDirect = Bun.file(directPath);
35116
35197
  const bunFileHtml = Bun.file(withHtml);
@@ -35119,7 +35200,7 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
35119
35200
  } else if (await bunFileHtml.exists()) {
35120
35201
  filePath = withHtml;
35121
35202
  } else {
35122
- const indexPath = path11.join(outputDir, pathname, "index.html");
35203
+ const indexPath = path12.join(outputDir, pathname, "index.html");
35123
35204
  const bunFileIndex = Bun.file(indexPath);
35124
35205
  if (await bunFileIndex.exists()) {
35125
35206
  filePath = indexPath;
@@ -35133,7 +35214,7 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
35133
35214
  }
35134
35215
  }
35135
35216
  console.log(`Serving file: ${filePath}`);
35136
- const extname = path11.extname(filePath);
35217
+ const extname = path12.extname(filePath);
35137
35218
  let contentType = "text/html";
35138
35219
  switch (extname) {
35139
35220
  case ".js":
@@ -35192,7 +35273,7 @@ var defaultDeps5 = {
35192
35273
  };
35193
35274
  async function handleServeCommand(options2, deps = defaultDeps5) {
35194
35275
  try {
35195
- const outputDir = path12.resolve(options2.output);
35276
+ const outputDir = path13.resolve(options2.output);
35196
35277
  const port = parseInt(options2.port, 10);
35197
35278
  await deps.startServer(outputDir, port);
35198
35279
  } catch (error) {