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.
- package/dist/cli/commands/validate.d.ts +5 -0
- package/dist/cli.js +134 -15
- package/dist/index.js +106 -14
- package/dist/parser.d.ts +6 -1
- package/dist/types.d.ts +2 -0
- package/dist/utils/markdown-utils.d.ts +11 -1
- package/package.json +1 -1
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
|
-
|
|
32894
|
-
|
|
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
|
-
|
|
32899
|
-
|
|
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
|
-
|
|
32921
|
-
|
|
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
|
|
32931
|
-
const
|
|
32932
|
-
|
|
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
|
-
|
|
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
|
|
33182
|
-
|
|
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
|
|
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
|
-
|
|
30512
|
-
|
|
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
|
-
|
|
30517
|
-
|
|
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
|
-
|
|
30539
|
-
|
|
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
|
|
30549
|
-
const
|
|
30550
|
-
|
|
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
|
-
|
|
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
|
|
31129
|
-
|
|
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
|
-
|
|
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
|
|
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