bunki 0.9.0 → 0.9.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.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Validate markdown files for parsing errors
3
+ */
4
+ import { Command } from "commander";
5
+ export declare function registerValidateCommand(program: Command): void;
package/dist/cli.js CHANGED
@@ -32890,13 +32890,31 @@ async function parseMarkdownFile(filePath) {
32890
32890
  try {
32891
32891
  const fileContent = await readFileAsText(filePath);
32892
32892
  if (fileContent === null) {
32893
- console.warn(`File not found or couldn't be read: ${filePath}`);
32894
- return null;
32893
+ return {
32894
+ post: null,
32895
+ error: {
32896
+ file: filePath,
32897
+ type: "file_not_found",
32898
+ message: "File not found or couldn't be read"
32899
+ }
32900
+ };
32895
32901
  }
32896
32902
  const { data, content } = import_gray_matter.default(fileContent);
32897
32903
  if (!data.title || !data.date) {
32898
- console.warn(`Skipping ${filePath}: missing required frontmatter (title or date)`);
32899
- return null;
32904
+ const missingFields = [];
32905
+ if (!data.title)
32906
+ missingFields.push("title");
32907
+ if (!data.date)
32908
+ missingFields.push("date");
32909
+ return {
32910
+ post: null,
32911
+ error: {
32912
+ file: filePath,
32913
+ type: "missing_field",
32914
+ message: `Missing required fields: ${missingFields.join(", ")}`,
32915
+ suggestion: "Add required frontmatter fields (title and date)"
32916
+ }
32917
+ };
32900
32918
  }
32901
32919
  let slug = data.slug || getBaseFilename(filePath);
32902
32920
  const sanitizedHtml = convertMarkdownToHtml(content);
@@ -32915,24 +32933,98 @@ async function parseMarkdownFile(filePath) {
32915
32933
  excerpt: data.excerpt || extractExcerpt(content),
32916
32934
  html: sanitizedHtml
32917
32935
  };
32918
- return post;
32936
+ return { post, error: null };
32919
32937
  } catch (error) {
32920
- console.error(`Error parsing markdown file ${filePath}:`, error);
32921
- return null;
32938
+ const isYamlError = error?.name === "YAMLException" || error?.message?.includes("YAML") || error?.message?.includes("mapping pair");
32939
+ let suggestion;
32940
+ if (isYamlError) {
32941
+ if (error?.message?.includes("mapping pair") || error?.message?.includes("colon")) {
32942
+ suggestion = 'Quote titles/descriptions containing colons (e.g., title: "My Post: A Guide")';
32943
+ } else if (error?.message?.includes("multiline key")) {
32944
+ suggestion = "Remove nested quotes or use single quotes inside double quotes";
32945
+ }
32946
+ }
32947
+ return {
32948
+ post: null,
32949
+ error: {
32950
+ file: filePath,
32951
+ type: isYamlError ? "yaml" : "unknown",
32952
+ message: error?.message || String(error),
32953
+ suggestion
32954
+ }
32955
+ };
32922
32956
  }
32923
32957
  }
32924
32958
 
32925
32959
  // src/parser.ts
32926
- async function parseMarkdownDirectory(contentDir) {
32960
+ async function parseMarkdownDirectory(contentDir, strictMode = false) {
32927
32961
  try {
32928
32962
  const markdownFiles = await findFilesByPattern("**/*.md", contentDir, true);
32929
32963
  console.log(`Found ${markdownFiles.length} markdown files`);
32930
- const postsPromises = markdownFiles.map((filePath) => parseMarkdownFile(filePath));
32931
- const posts = await Promise.all(postsPromises);
32932
- return posts.filter((post) => post !== null).sort((a, b2) => new Date(b2.date).getTime() - new Date(a.date).getTime());
32964
+ const resultsPromises = markdownFiles.map((filePath) => parseMarkdownFile(filePath));
32965
+ const results = await Promise.all(resultsPromises);
32966
+ const posts = [];
32967
+ const errors = [];
32968
+ for (const result of results) {
32969
+ if (result.post) {
32970
+ posts.push(result.post);
32971
+ } else if (result.error) {
32972
+ errors.push(result.error);
32973
+ }
32974
+ }
32975
+ if (errors.length > 0) {
32976
+ console.error(`
32977
+ \u26A0\uFE0F Found ${errors.length} parsing error(s):
32978
+ `);
32979
+ const yamlErrors = errors.filter((e) => e.type === "yaml");
32980
+ const missingFieldErrors = errors.filter((e) => e.type === "missing_field");
32981
+ const otherErrors = errors.filter((e) => e.type !== "yaml" && e.type !== "missing_field");
32982
+ if (yamlErrors.length > 0) {
32983
+ console.error(` YAML Parsing Errors (${yamlErrors.length}):`);
32984
+ yamlErrors.slice(0, 5).forEach((e) => {
32985
+ console.error(` \u274C ${e.file}`);
32986
+ if (e.suggestion) {
32987
+ console.error(` \uD83D\uDCA1 ${e.suggestion}`);
32988
+ }
32989
+ });
32990
+ if (yamlErrors.length > 5) {
32991
+ console.error(` ... and ${yamlErrors.length - 5} more`);
32992
+ }
32993
+ console.error("");
32994
+ }
32995
+ if (missingFieldErrors.length > 0) {
32996
+ console.error(` Missing Required Fields (${missingFieldErrors.length}):`);
32997
+ missingFieldErrors.slice(0, 5).forEach((e) => {
32998
+ console.error(` \u26A0\uFE0F ${e.file}: ${e.message}`);
32999
+ });
33000
+ if (missingFieldErrors.length > 5) {
33001
+ console.error(` ... and ${missingFieldErrors.length - 5} more`);
33002
+ }
33003
+ console.error("");
33004
+ }
33005
+ if (otherErrors.length > 0) {
33006
+ console.error(` Other Errors (${otherErrors.length}):`);
33007
+ otherErrors.slice(0, 3).forEach((e) => {
33008
+ console.error(` \u274C ${e.file}: ${e.message}`);
33009
+ });
33010
+ if (otherErrors.length > 3) {
33011
+ console.error(` ... and ${otherErrors.length - 3} more`);
33012
+ }
33013
+ console.error("");
33014
+ }
33015
+ console.error(`\uD83D\uDCDD Tip: Fix YAML errors by quoting titles/descriptions with colons`);
33016
+ console.error(` Example: title: "My Post: A Guide" (quotes required for colons)
33017
+ `);
33018
+ if (strictMode) {
33019
+ throw new Error(`Build failed: ${errors.length} parsing error(s) found (strictMode enabled)`);
33020
+ }
33021
+ }
33022
+ const sortedPosts = posts.sort((a, b2) => new Date(b2.date).getTime() - new Date(a.date).getTime());
33023
+ console.log(`Parsed ${sortedPosts.length} posts`);
33024
+ return sortedPosts;
32933
33025
  } catch (error) {
32934
33026
  console.error(`Error parsing markdown directory:`, error);
32935
- return [];
33027
+ throw error;
32936
33028
  }
32937
33029
  }
32938
33030
 
@@ -33178,8 +33270,8 @@ class SiteGenerator {
33178
33270
  console.warn("Error loading tag descriptions:", error);
33179
33271
  }
33180
33272
  }
33181
- const posts = await parseMarkdownDirectory(this.options.contentDir);
33182
- console.log(`Parsed ${posts.length} posts`);
33273
+ const strictMode = this.options.config.strictMode ?? false;
33274
+ const posts = await parseMarkdownDirectory(this.options.contentDir, strictMode);
33183
33275
  const tags = {};
33184
33276
  posts.forEach((post) => {
33185
33277
  post.tagSlugs = {};
@@ -34690,6 +34782,32 @@ function registerServeCommand(program2) {
34690
34782
  });
34691
34783
  }
34692
34784
 
34785
+ // src/cli/commands/validate.ts
34786
+ function registerValidateCommand(program2) {
34787
+ program2.command("validate").description("Validate markdown files for parsing errors").option("-c, --config <path>", "Path to config file", "bunki.config.ts").action(async (options2) => {
34788
+ try {
34789
+ const config = await loadConfig(options2.config);
34790
+ console.log(`\uD83D\uDD0D Validating markdown files...
34791
+ `);
34792
+ const contentDir = "./content";
34793
+ try {
34794
+ await parseMarkdownDirectory(contentDir, true);
34795
+ console.log(`
34796
+ \u2705 All markdown files are valid!`);
34797
+ process.exit(0);
34798
+ } catch (error) {
34799
+ console.error(`
34800
+ \u274C Validation failed
34801
+ `);
34802
+ process.exit(1);
34803
+ }
34804
+ } catch (error) {
34805
+ console.error("Error during validation:", error.message);
34806
+ process.exit(1);
34807
+ }
34808
+ });
34809
+ }
34810
+
34693
34811
  // src/cli.ts
34694
34812
  var program2 = new Command;
34695
34813
  registerInitCommand(program2);
@@ -34698,7 +34816,8 @@ registerGenerateCommand(program2);
34698
34816
  registerServeCommand(program2);
34699
34817
  registerCssCommand(program2);
34700
34818
  registerImagesPushCommand(program2);
34701
- program2.name("bunki").description("An opinionated static site generator built with Bun").version("0.5.3");
34819
+ registerValidateCommand(program2);
34820
+ program2.name("bunki").description("An opinionated static site generator built with Bun").version("0.9.1");
34702
34821
  var currentFile = import.meta.url.replace("file://", "");
34703
34822
  var mainFile = Bun.main;
34704
34823
  if (currentFile === mainFile || currentFile.endsWith(mainFile)) {
package/dist/index.js CHANGED
@@ -30508,13 +30508,31 @@ async function parseMarkdownFile(filePath) {
30508
30508
  try {
30509
30509
  const fileContent = await readFileAsText(filePath);
30510
30510
  if (fileContent === null) {
30511
- console.warn(`File not found or couldn't be read: ${filePath}`);
30512
- return null;
30511
+ return {
30512
+ post: null,
30513
+ error: {
30514
+ file: filePath,
30515
+ type: "file_not_found",
30516
+ message: "File not found or couldn't be read"
30517
+ }
30518
+ };
30513
30519
  }
30514
30520
  const { data, content } = import_gray_matter.default(fileContent);
30515
30521
  if (!data.title || !data.date) {
30516
- console.warn(`Skipping ${filePath}: missing required frontmatter (title or date)`);
30517
- return null;
30522
+ const missingFields = [];
30523
+ if (!data.title)
30524
+ missingFields.push("title");
30525
+ if (!data.date)
30526
+ missingFields.push("date");
30527
+ return {
30528
+ post: null,
30529
+ error: {
30530
+ file: filePath,
30531
+ type: "missing_field",
30532
+ message: `Missing required fields: ${missingFields.join(", ")}`,
30533
+ suggestion: "Add required frontmatter fields (title and date)"
30534
+ }
30535
+ };
30518
30536
  }
30519
30537
  let slug = data.slug || getBaseFilename(filePath);
30520
30538
  const sanitizedHtml = convertMarkdownToHtml(content);
@@ -30533,24 +30551,98 @@ async function parseMarkdownFile(filePath) {
30533
30551
  excerpt: data.excerpt || extractExcerpt(content),
30534
30552
  html: sanitizedHtml
30535
30553
  };
30536
- return post;
30554
+ return { post, error: null };
30537
30555
  } catch (error) {
30538
- console.error(`Error parsing markdown file ${filePath}:`, error);
30539
- return null;
30556
+ const isYamlError = error?.name === "YAMLException" || error?.message?.includes("YAML") || error?.message?.includes("mapping pair");
30557
+ let suggestion;
30558
+ if (isYamlError) {
30559
+ if (error?.message?.includes("mapping pair") || error?.message?.includes("colon")) {
30560
+ suggestion = 'Quote titles/descriptions containing colons (e.g., title: "My Post: A Guide")';
30561
+ } else if (error?.message?.includes("multiline key")) {
30562
+ suggestion = "Remove nested quotes or use single quotes inside double quotes";
30563
+ }
30564
+ }
30565
+ return {
30566
+ post: null,
30567
+ error: {
30568
+ file: filePath,
30569
+ type: isYamlError ? "yaml" : "unknown",
30570
+ message: error?.message || String(error),
30571
+ suggestion
30572
+ }
30573
+ };
30540
30574
  }
30541
30575
  }
30542
30576
 
30543
30577
  // src/parser.ts
30544
- async function parseMarkdownDirectory(contentDir) {
30578
+ async function parseMarkdownDirectory(contentDir, strictMode = false) {
30545
30579
  try {
30546
30580
  const markdownFiles = await findFilesByPattern("**/*.md", contentDir, true);
30547
30581
  console.log(`Found ${markdownFiles.length} markdown files`);
30548
- const postsPromises = markdownFiles.map((filePath) => parseMarkdownFile(filePath));
30549
- const posts = await Promise.all(postsPromises);
30550
- return posts.filter((post) => post !== null).sort((a, b2) => new Date(b2.date).getTime() - new Date(a.date).getTime());
30582
+ const resultsPromises = markdownFiles.map((filePath) => parseMarkdownFile(filePath));
30583
+ const results = await Promise.all(resultsPromises);
30584
+ const posts = [];
30585
+ const errors = [];
30586
+ for (const result of results) {
30587
+ if (result.post) {
30588
+ posts.push(result.post);
30589
+ } else if (result.error) {
30590
+ errors.push(result.error);
30591
+ }
30592
+ }
30593
+ if (errors.length > 0) {
30594
+ console.error(`
30595
+ \u26A0\uFE0F Found ${errors.length} parsing error(s):
30596
+ `);
30597
+ const yamlErrors = errors.filter((e) => e.type === "yaml");
30598
+ const missingFieldErrors = errors.filter((e) => e.type === "missing_field");
30599
+ const otherErrors = errors.filter((e) => e.type !== "yaml" && e.type !== "missing_field");
30600
+ if (yamlErrors.length > 0) {
30601
+ console.error(` YAML Parsing Errors (${yamlErrors.length}):`);
30602
+ yamlErrors.slice(0, 5).forEach((e) => {
30603
+ console.error(` \u274C ${e.file}`);
30604
+ if (e.suggestion) {
30605
+ console.error(` \uD83D\uDCA1 ${e.suggestion}`);
30606
+ }
30607
+ });
30608
+ if (yamlErrors.length > 5) {
30609
+ console.error(` ... and ${yamlErrors.length - 5} more`);
30610
+ }
30611
+ console.error("");
30612
+ }
30613
+ if (missingFieldErrors.length > 0) {
30614
+ console.error(` Missing Required Fields (${missingFieldErrors.length}):`);
30615
+ missingFieldErrors.slice(0, 5).forEach((e) => {
30616
+ console.error(` \u26A0\uFE0F ${e.file}: ${e.message}`);
30617
+ });
30618
+ if (missingFieldErrors.length > 5) {
30619
+ console.error(` ... and ${missingFieldErrors.length - 5} more`);
30620
+ }
30621
+ console.error("");
30622
+ }
30623
+ if (otherErrors.length > 0) {
30624
+ console.error(` Other Errors (${otherErrors.length}):`);
30625
+ otherErrors.slice(0, 3).forEach((e) => {
30626
+ console.error(` \u274C ${e.file}: ${e.message}`);
30627
+ });
30628
+ if (otherErrors.length > 3) {
30629
+ console.error(` ... and ${otherErrors.length - 3} more`);
30630
+ }
30631
+ console.error("");
30632
+ }
30633
+ console.error(`\uD83D\uDCDD Tip: Fix YAML errors by quoting titles/descriptions with colons`);
30634
+ console.error(` Example: title: "My Post: A Guide" (quotes required for colons)
30635
+ `);
30636
+ if (strictMode) {
30637
+ throw new Error(`Build failed: ${errors.length} parsing error(s) found (strictMode enabled)`);
30638
+ }
30639
+ }
30640
+ const sortedPosts = posts.sort((a, b2) => new Date(b2.date).getTime() - new Date(a.date).getTime());
30641
+ console.log(`Parsed ${sortedPosts.length} posts`);
30642
+ return sortedPosts;
30551
30643
  } catch (error) {
30552
30644
  console.error(`Error parsing markdown directory:`, error);
30553
- return [];
30645
+ throw error;
30554
30646
  }
30555
30647
  }
30556
30648
  // src/server.ts
@@ -31125,8 +31217,8 @@ class SiteGenerator {
31125
31217
  console.warn("Error loading tag descriptions:", error);
31126
31218
  }
31127
31219
  }
31128
- const posts = await parseMarkdownDirectory(this.options.contentDir);
31129
- console.log(`Parsed ${posts.length} posts`);
31220
+ const strictMode = this.options.config.strictMode ?? false;
31221
+ const posts = await parseMarkdownDirectory(this.options.contentDir, strictMode);
31130
31222
  const tags = {};
31131
31223
  posts.forEach((post) => {
31132
31224
  post.tagSlugs = {};
package/dist/parser.d.ts CHANGED
@@ -1,2 +1,7 @@
1
1
  import { Post } from "./types";
2
- export declare function parseMarkdownDirectory(contentDir: string): Promise<Post[]>;
2
+ import { type ParseError } from "./utils/markdown-utils";
3
+ export interface ParseResult {
4
+ posts: Post[];
5
+ errors: ParseError[];
6
+ }
7
+ export declare function parseMarkdownDirectory(contentDir: string, strictMode?: boolean): Promise<Post[]>;
package/dist/types.d.ts CHANGED
@@ -68,6 +68,8 @@ export interface SiteConfig {
68
68
  webMaster?: string;
69
69
  /** Copyright statement for RSS feed (e.g., "Copyright © 2025 Your Site Name") */
70
70
  copyright?: string;
71
+ /** Strict mode: fail build on parsing errors (default: false) */
72
+ strictMode?: boolean;
71
73
  /** Additional custom configuration options */
72
74
  [key: string]: any;
73
75
  }
@@ -2,4 +2,14 @@ import { Post } from "../types";
2
2
  export declare function setNoFollowExceptions(exceptions: string[]): void;
3
3
  export declare function extractExcerpt(content: string, maxLength?: number): string;
4
4
  export declare function convertMarkdownToHtml(markdownContent: string): string;
5
- export declare function parseMarkdownFile(filePath: string): Promise<Post | null>;
5
+ export interface ParseError {
6
+ file: string;
7
+ type: "yaml" | "missing_field" | "file_not_found" | "unknown";
8
+ message: string;
9
+ suggestion?: string;
10
+ }
11
+ export interface ParseMarkdownResult {
12
+ post: Post | null;
13
+ error: ParseError | null;
14
+ }
15
+ export declare function parseMarkdownFile(filePath: string): Promise<ParseMarkdownResult>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunki",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
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",