bunki 0.19.0 → 0.19.2

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
@@ -33017,9 +33017,11 @@ var import_sanitize_html = __toESM(require_sanitize_html(), 1);
33017
33017
 
33018
33018
  // src/utils/markdown/constants.ts
33019
33019
  var RELATIVE_LINK_REGEX = /^(\.\.\/)+(\d{4})\/([a-zA-Z0-9_-]+?)(?:\.md)?(?:\/)?(#[^#]*)?$/;
33020
+ var SAME_DIR_LINK_REGEX = /^\.\/([a-zA-Z0-9_-]+?)(?:\.md)?(?:\/)?(#[^#]*)?$/;
33020
33021
  var IMAGE_PATH_REGEX = /^\.\.\/\.\.\/assets\/(\d{4})\/([^/]+)\/(.+)$/;
33021
33022
  var IMAGE_PATH_ASSETS_DIR = /^\.\.\/\_assets\/(.+)$/;
33022
33023
  var IMAGE_PATH_ASSETS_SAME_DIR = /^\.\/\_assets\/(.+)$/;
33024
+ var IMAGE_PATH_CROSS_YEAR_ASSETS = /^\.\.\/(\d{4})\/_assets\/(.+)$/;
33023
33025
  var YOUTUBE_EMBED_REGEX = /<a href="(https?:\/\/(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)[^"]*)"[^>]*>(.*?)<\/a>/g;
33024
33026
  var EXTERNAL_LINK_REGEX = /<a href="(https?:\/\/|\/\/)([^"]+)"/g;
33025
33027
  var SCHEMA_ORG_PLACE_TYPES = new Set([
@@ -33097,6 +33099,12 @@ function transformImagePath(relativePath, config) {
33097
33099
  const path5 = config.pathPattern.replace("{year}", config.postYear).replace("{slug}", "").replace("{filename}", filename).replace(/\/+/g, "/").replace(/^\//, "");
33098
33100
  return `${config.baseUrl}/${path5}`;
33099
33101
  }
33102
+ const crossYearMatch = relativePath.match(IMAGE_PATH_CROSS_YEAR_ASSETS);
33103
+ if (crossYearMatch) {
33104
+ const [, year, filename] = crossYearMatch;
33105
+ const path5 = config.pathPattern.replace("{year}", year).replace("{slug}", "").replace("{filename}", filename).replace(/\/+/g, "/").replace(/^\//, "");
33106
+ return `${config.baseUrl}/${path5}`;
33107
+ }
33100
33108
  return null;
33101
33109
  }
33102
33110
  function createMarked(cdnConfig) {
@@ -33127,6 +33135,11 @@ function createMarked(cdnConfig) {
33127
33135
  const [, , year, slug, anchor = ""] = relativeMatch;
33128
33136
  token.href = `/${year}/${slug}/${anchor}`;
33129
33137
  }
33138
+ const sameDirMatch = token.href.match(SAME_DIR_LINK_REGEX);
33139
+ if (sameDirMatch && cdnConfig?.postYear) {
33140
+ const [, slug, anchor = ""] = sameDirMatch;
33141
+ token.href = `/${cdnConfig.postYear}/${slug}/${anchor}`;
33142
+ }
33130
33143
  const isExternal = token.href && (token.href.startsWith("http://") || token.href.startsWith("https://") || token.href.startsWith("//"));
33131
33144
  if (isExternal) {
33132
33145
  token.isExternalLink = true;
@@ -33137,7 +33150,7 @@ function createMarked(cdnConfig) {
33137
33150
  }
33138
33151
  if (token.type === "image" && cdnConfig?.enabled) {
33139
33152
  const href = token.href || "";
33140
- if (href.startsWith("../../assets/") || href.startsWith("../_assets/") || href.startsWith("./_assets/")) {
33153
+ if (href.startsWith("../../assets/") || href.startsWith("../_assets/") || href.startsWith("./_assets/") || IMAGE_PATH_CROSS_YEAR_ASSETS.test(href)) {
33141
33154
  const transformed = transformImagePath(href, cdnConfig);
33142
33155
  if (transformed) {
33143
33156
  token.href = transformed;
@@ -33272,12 +33285,13 @@ function validateBusinessLocation(business, filePath) {
33272
33285
  suggestion: "Add 'type: Restaurant' (or Market, Park, Hotel, Museum, Cafe, Zoo, etc.) to frontmatter"
33273
33286
  };
33274
33287
  }
33275
- if (!SCHEMA_ORG_PLACE_TYPES.has(loc.type)) {
33288
+ const locType = String(loc.type);
33289
+ if (!SCHEMA_ORG_PLACE_TYPES.has(locType)) {
33276
33290
  const exampleTypes = Array.from(SCHEMA_ORG_PLACE_TYPES).slice(0, 10);
33277
33291
  return {
33278
33292
  file: filePath,
33279
33293
  type: "validation",
33280
- message: `Invalid business type '${loc.type}' in business${locIndex}`,
33294
+ message: `Invalid business type '${locType}' in business${locIndex}`,
33281
33295
  suggestion: `Use a valid Schema.org Place type: ${exampleTypes.join(", ")}, etc.`
33282
33296
  };
33283
33297
  }
@@ -33435,12 +33449,14 @@ async function parseMarkdownFile(filePath, cdnConfig) {
33435
33449
  };
33436
33450
  return { post, error: null };
33437
33451
  } catch (error) {
33438
- const isYamlError = error?.name === "YAMLException" || error?.message?.includes("YAML") || error?.message?.includes("mapping pair");
33452
+ const msg = error instanceof Error ? error.message : String(error);
33453
+ const name = error instanceof Error ? error.name : "";
33454
+ const isYamlError = name === "YAMLException" || msg.includes("YAML") || msg.includes("mapping pair");
33439
33455
  let suggestion;
33440
33456
  if (isYamlError) {
33441
- if (error?.message?.includes("mapping pair") || error?.message?.includes("colon")) {
33457
+ if (msg.includes("mapping pair") || msg.includes("colon")) {
33442
33458
  suggestion = 'Quote titles/descriptions containing colons (e.g., title: "My Post: A Guide")';
33443
- } else if (error?.message?.includes("multiline key")) {
33459
+ } else if (msg.includes("multiline key")) {
33444
33460
  suggestion = "Remove nested quotes or use single quotes inside double quotes";
33445
33461
  }
33446
33462
  }
@@ -33449,7 +33465,7 @@ async function parseMarkdownFile(filePath, cdnConfig) {
33449
33465
  error: {
33450
33466
  file: filePath,
33451
33467
  type: isYamlError ? "yaml" : "unknown",
33452
- message: error?.message || String(error),
33468
+ message: msg,
33453
33469
  suggestion
33454
33470
  }
33455
33471
  };
@@ -33457,6 +33473,22 @@ async function parseMarkdownFile(filePath, cdnConfig) {
33457
33473
  }
33458
33474
 
33459
33475
  // src/parser.ts
33476
+ function logErrorGroup(label, errors, opts) {
33477
+ if (errors.length === 0)
33478
+ return;
33479
+ console.error(` ${label} (${errors.length}):`);
33480
+ errors.slice(0, opts.limit).forEach((e) => {
33481
+ const msg = opts.showMessage ? `: ${e.message}` : "";
33482
+ console.error(` ${opts.icon} ${e.file}${msg}`);
33483
+ if (opts.showSuggestion && e.suggestion) {
33484
+ console.error(` \uD83D\uDCA1 ${e.suggestion}`);
33485
+ }
33486
+ });
33487
+ if (errors.length > opts.limit) {
33488
+ console.error(` ... and ${errors.length - opts.limit} more`);
33489
+ }
33490
+ console.error("");
33491
+ }
33460
33492
  function detectFileConflicts(files) {
33461
33493
  const errors = [];
33462
33494
  const slugMap = new Map;
@@ -33535,52 +33567,30 @@ async function parseMarkdownDirectory(contentDir, strictMode = false, cdnConfig)
33535
33567
  const missingFieldErrors = errors.filter((e) => e.type === "missing_field");
33536
33568
  const validationErrors = errors.filter((e) => e.type === "validation");
33537
33569
  const otherErrors = errors.filter((e) => e.type !== "yaml" && e.type !== "missing_field" && e.type !== "validation");
33538
- if (yamlErrors.length > 0) {
33539
- console.error(` YAML Parsing Errors (${yamlErrors.length}):`);
33540
- yamlErrors.slice(0, 5).forEach((e) => {
33541
- console.error(` \u274C ${e.file}`);
33542
- if (e.suggestion) {
33543
- console.error(` \uD83D\uDCA1 ${e.suggestion}`);
33544
- }
33545
- });
33546
- if (yamlErrors.length > 5) {
33547
- console.error(` ... and ${yamlErrors.length - 5} more`);
33548
- }
33549
- console.error("");
33550
- }
33551
- if (missingFieldErrors.length > 0) {
33552
- console.error(` Missing Required Fields (${missingFieldErrors.length}):`);
33553
- missingFieldErrors.slice(0, 5).forEach((e) => {
33554
- console.error(` \u26A0\uFE0F ${e.file}: ${e.message}`);
33555
- });
33556
- if (missingFieldErrors.length > 5) {
33557
- console.error(` ... and ${missingFieldErrors.length - 5} more`);
33558
- }
33559
- console.error("");
33560
- }
33561
- if (validationErrors.length > 0) {
33562
- console.error(` Validation Errors (${validationErrors.length}):`);
33563
- validationErrors.slice(0, 5).forEach((e) => {
33564
- console.error(` \u26A0\uFE0F ${e.file}: ${e.message}`);
33565
- if (e.suggestion) {
33566
- console.error(` \uD83D\uDCA1 ${e.suggestion}`);
33567
- }
33568
- });
33569
- if (validationErrors.length > 5) {
33570
- console.error(` ... and ${validationErrors.length - 5} more`);
33571
- }
33572
- console.error("");
33573
- }
33574
- if (otherErrors.length > 0) {
33575
- console.error(` Other Errors (${otherErrors.length}):`);
33576
- otherErrors.slice(0, 3).forEach((e) => {
33577
- console.error(` \u274C ${e.file}: ${e.message}`);
33578
- });
33579
- if (otherErrors.length > 3) {
33580
- console.error(` ... and ${otherErrors.length - 3} more`);
33581
- }
33582
- console.error("");
33583
- }
33570
+ logErrorGroup("YAML Parsing Errors", yamlErrors, {
33571
+ icon: "\u274C",
33572
+ showMessage: false,
33573
+ showSuggestion: true,
33574
+ limit: 5
33575
+ });
33576
+ logErrorGroup("Missing Required Fields", missingFieldErrors, {
33577
+ icon: "\u26A0\uFE0F ",
33578
+ showMessage: true,
33579
+ showSuggestion: false,
33580
+ limit: 5
33581
+ });
33582
+ logErrorGroup("Validation Errors", validationErrors, {
33583
+ icon: "\u26A0\uFE0F ",
33584
+ showMessage: true,
33585
+ showSuggestion: true,
33586
+ limit: 5
33587
+ });
33588
+ logErrorGroup("Other Errors", otherErrors, {
33589
+ icon: "\u274C",
33590
+ showMessage: true,
33591
+ showSuggestion: false,
33592
+ limit: 3
33593
+ });
33584
33594
  console.error(`\uD83D\uDCDD Tip: Fix YAML errors by quoting titles/descriptions with colons`);
33585
33595
  console.error(` Example: title: "My Post: A Guide" (quotes required for colons)
33586
33596
  `);
@@ -34168,6 +34178,19 @@ async function writeHtmlFile(outputDir, relativePath, content) {
34168
34178
  await ensureDir(dir);
34169
34179
  await Bun.write(fullPath, content);
34170
34180
  }
34181
+ async function generateOptionalPage(templateName, context, outputDir, outputPath, label) {
34182
+ try {
34183
+ const html = import_nunjucks.default.render(templateName, context);
34184
+ await writeHtmlFile(outputDir, outputPath, html);
34185
+ console.log(`Generated ${label}`);
34186
+ } catch (error) {
34187
+ if (error instanceof Error && error.message.includes(templateName)) {
34188
+ console.log(`No ${templateName} template found, skipping ${label}`);
34189
+ } else {
34190
+ console.warn(`Error generating ${label}:`, error);
34191
+ }
34192
+ }
34193
+ }
34171
34194
  async function generateIndexPages(site, config, outputDir, pageSize = PAGINATION.DEFAULT_PAGE_SIZE) {
34172
34195
  const totalPages = getTotalPages(site.posts.length, pageSize);
34173
34196
  for (let page = 1;page <= totalPages; page++) {
@@ -34276,35 +34299,10 @@ async function generateYearArchives(site, config, outputDir, pageSize = PAGINATI
34276
34299
  }
34277
34300
  }
34278
34301
  async function generate404Page(config, outputDir) {
34279
- try {
34280
- const notFoundHtml = import_nunjucks.default.render("404.njk", {
34281
- site: config
34282
- });
34283
- await writeHtmlFile(outputDir, "404.html", notFoundHtml);
34284
- console.log("Generated 404.html");
34285
- } catch (error) {
34286
- if (error instanceof Error && error.message.includes("404.njk")) {
34287
- console.log("No 404.njk template found, skipping 404 page generation");
34288
- } else {
34289
- console.warn("Error generating 404 page:", error);
34290
- }
34291
- }
34302
+ await generateOptionalPage("404.njk", { site: config }, outputDir, "404.html", "404.html");
34292
34303
  }
34293
34304
  async function generateMapPage(site, config, outputDir) {
34294
- try {
34295
- const mapHtml = import_nunjucks.default.render("map.njk", {
34296
- site: config,
34297
- posts: site.posts
34298
- });
34299
- await writeHtmlFile(outputDir, "map/index.html", mapHtml);
34300
- console.log("Generated map page");
34301
- } catch (error) {
34302
- if (error instanceof Error && error.message.includes("map.njk")) {
34303
- console.log("No map.njk template found, skipping map page generation");
34304
- } else {
34305
- console.warn("Error generating map page:", error);
34306
- }
34307
- }
34305
+ await generateOptionalPage("map.njk", { site: config, posts: site.posts }, outputDir, "map/index.html", "map page");
34308
34306
  }
34309
34307
 
34310
34308
  // src/generators/assets.ts
@@ -35722,18 +35720,9 @@ function registerNewCommand(program2) {
35722
35720
  import path17 from "path";
35723
35721
 
35724
35722
  // src/server.ts
35725
- import fs2 from "fs";
35726
35723
  import path16 from "path";
35727
35724
  async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
35728
- try {
35729
- const stats = await fs2.promises.stat(outputDir);
35730
- if (!stats.isDirectory()) {
35731
- const msg = `Error: Output directory ${outputDir} does not exist or is not accessible.`;
35732
- console.error(msg);
35733
- console.log('Try running "bunki generate" first to build your site.');
35734
- throw new Error(msg);
35735
- }
35736
- } catch (error) {
35725
+ if (!await isDirectory(outputDir)) {
35737
35726
  const msg = `Error: Output directory ${outputDir} does not exist or is not accessible.`;
35738
35727
  console.error(msg);
35739
35728
  console.log('Try running "bunki generate" first to build your site.');
@@ -3,9 +3,11 @@
3
3
  * Extracted for better code organization and performance
4
4
  */
5
5
  export declare const RELATIVE_LINK_REGEX: RegExp;
6
+ export declare const SAME_DIR_LINK_REGEX: RegExp;
6
7
  export declare const IMAGE_PATH_REGEX: RegExp;
7
8
  export declare const IMAGE_PATH_ASSETS_DIR: RegExp;
8
9
  export declare const IMAGE_PATH_ASSETS_SAME_DIR: RegExp;
10
+ export declare const IMAGE_PATH_CROSS_YEAR_ASSETS: RegExp;
9
11
  export declare const YOUTUBE_EMBED_REGEX: RegExp;
10
12
  export declare const EXTERNAL_LINK_REGEX: RegExp;
11
13
  export declare const SCHEMA_ORG_PLACE_TYPES: Set<string>;
@@ -14,7 +14,7 @@ export interface ValidationError {
14
14
  * @param filePath - File path for error reporting
15
15
  * @returns ValidationError if invalid, null if valid
16
16
  */
17
- export declare function validateBusinessLocation(business: any, filePath: string): ValidationError | null;
17
+ export declare function validateBusinessLocation(business: unknown, filePath: string): ValidationError | null;
18
18
  /**
19
19
  * Validate that tags don't contain spaces (must use hyphens)
20
20
  * @param tags - Array of tag strings
@@ -28,4 +28,4 @@ export declare function validateTags(tags: string[], filePath: string): Validati
28
28
  * @param filePath - File path for error reporting
29
29
  * @returns ValidationError if found, null otherwise
30
30
  */
31
- export declare function checkDeprecatedLocationField(data: any, filePath: string): ValidationError | null;
31
+ export declare function checkDeprecatedLocationField(data: Record<string, unknown>, filePath: string): ValidationError | null;
@@ -20,7 +20,7 @@ export interface PaginationData {
20
20
  * @param pagePath - Base path for pagination (e.g., "/", "/tags/tech/")
21
21
  * @returns Pagination data object
22
22
  */
23
- export declare function createPagination<T>(items: T[], currentPage: number, pageSize: number, pagePath: string): PaginationData;
23
+ export declare function createPagination(items: readonly unknown[], currentPage: number, pageSize: number, pagePath: string): PaginationData;
24
24
  /**
25
25
  * Get paginated slice of items for a specific page
26
26
  * @param items - Array of items to paginate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunki",
3
- "version": "0.19.0",
3
+ "version": "0.19.2",
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",