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 +32 -4
- package/dist/cli.js +161 -80
- package/dist/index.js +408 -327
- package/dist/parser.d.ts +2 -2
- package/dist/types.d.ts +13 -0
- package/dist/utils/file-utils.d.ts +4 -2
- package/dist/utils/markdown-utils.d.ts +3 -3
- package/package.json +1 -1
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/`
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
33200
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
33803
|
+
await Bun.write(path6.join(yearDir, "index.html"), yearPageHtml);
|
|
33723
33804
|
} else {
|
|
33724
|
-
const pageDir =
|
|
33805
|
+
const pageDir = path6.join(yearDir, "page", page.toString());
|
|
33725
33806
|
await ensureDir(pageDir);
|
|
33726
|
-
await Bun.write(
|
|
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(
|
|
33837
|
+
await Bun.write(path6.join(this.options.outputDir, "index.html"), pageHtml);
|
|
33757
33838
|
} else {
|
|
33758
|
-
const pageDir =
|
|
33839
|
+
const pageDir = path6.join(this.options.outputDir, "page", page.toString());
|
|
33759
33840
|
await ensureDir(pageDir);
|
|
33760
|
-
await Bun.write(
|
|
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 =
|
|
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(
|
|
33863
|
+
await Bun.write(path6.join(postDir, "index.html"), postHtml);
|
|
33783
33864
|
}
|
|
33784
33865
|
}
|
|
33785
33866
|
async generateTagPages() {
|
|
33786
|
-
const tagsDir =
|
|
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(
|
|
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 =
|
|
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(
|
|
33918
|
+
await Bun.write(path6.join(tagDir, "index.html"), tagPageHtml);
|
|
33838
33919
|
} else {
|
|
33839
|
-
const pageDir =
|
|
33920
|
+
const pageDir = path6.join(tagDir, "page", page.toString());
|
|
33840
33921
|
await ensureDir(pageDir);
|
|
33841
|
-
await Bun.write(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
33893
|
-
const outputDir =
|
|
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 =
|
|
33903
|
-
const publicDir =
|
|
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 =
|
|
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 =
|
|
33922
|
-
const targetPath =
|
|
33923
|
-
const targetDir =
|
|
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 =
|
|
33935
|
-
const relativePath =
|
|
33936
|
-
const destPath =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
34221
|
-
const contentDir =
|
|
34222
|
-
const outputDir =
|
|
34223
|
-
const templatesDir =
|
|
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
|
|
34332
|
+
import path9 from "path";
|
|
34252
34333
|
|
|
34253
34334
|
// src/utils/s3-uploader.ts
|
|
34254
34335
|
var {S3Client } = globalThis.Bun;
|
|
34255
|
-
import
|
|
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 =
|
|
34379
|
-
const filename =
|
|
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 =
|
|
34494
|
+
var DEFAULT_IMAGES_DIR = path9.join(process.cwd(), "assets");
|
|
34414
34495
|
async function uploadImages(options2 = {}) {
|
|
34415
34496
|
try {
|
|
34416
|
-
const imagesDir =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
34519
|
-
const templatesDir =
|
|
34520
|
-
const stylesDir =
|
|
34521
|
-
const publicDir =
|
|
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(
|
|
34608
|
+
await deps.writeFile(path10.join(templatesDir, filename), content);
|
|
34528
34609
|
}
|
|
34529
|
-
await deps.writeFile(
|
|
34530
|
-
await deps.writeFile(
|
|
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
|
|
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 =
|
|
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
|
|
35144
|
+
import path13 from "path";
|
|
35064
35145
|
|
|
35065
35146
|
// src/server.ts
|
|
35066
35147
|
import fs3 from "fs";
|
|
35067
|
-
import
|
|
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 =
|
|
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 =
|
|
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 =
|
|
35191
|
+
filePath = path12.join(outputDir, year, "page", pageNumber, "index.html");
|
|
35111
35192
|
} else {
|
|
35112
|
-
const directPath =
|
|
35113
|
-
const withoutSlash =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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) {
|