bunki 0.16.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 +134 -71
- package/dist/index.js +381 -318
- package/dist/utils/file-utils.d.ts +4 -2
- 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 {
|
|
@@ -33214,6 +33222,20 @@ async function parseMarkdownFile(filePath, cdnConfig) {
|
|
|
33214
33222
|
};
|
|
33215
33223
|
}
|
|
33216
33224
|
}
|
|
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
|
+
}
|
|
33217
33239
|
let slug = getBaseFilename(filePath);
|
|
33218
33240
|
const sanitizedHtml = convertMarkdownToHtml(content, cdnConfig);
|
|
33219
33241
|
const pacificDate = toPacificTime(data.date);
|
|
@@ -33277,14 +33299,55 @@ async function parseMarkdownFile(filePath, cdnConfig) {
|
|
|
33277
33299
|
}
|
|
33278
33300
|
|
|
33279
33301
|
// src/parser.ts
|
|
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
|
+
}
|
|
33280
33327
|
async function parseMarkdownDirectory(contentDir, strictMode = false, cdnConfig) {
|
|
33281
33328
|
try {
|
|
33282
33329
|
const markdownFiles = await findFilesByPattern("**/*.md", contentDir, true);
|
|
33283
33330
|
console.log(`Found ${markdownFiles.length} markdown files`);
|
|
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
|
+
}
|
|
33284
33347
|
const resultsPromises = markdownFiles.map((filePath) => parseMarkdownFile(filePath, cdnConfig));
|
|
33285
33348
|
const results = await Promise.all(resultsPromises);
|
|
33286
33349
|
const posts = [];
|
|
33287
|
-
const errors = [];
|
|
33350
|
+
const errors = [...conflictErrors];
|
|
33288
33351
|
for (const result of results) {
|
|
33289
33352
|
if (result.post) {
|
|
33290
33353
|
posts.push(result.post);
|
|
@@ -33618,7 +33681,7 @@ class SiteGenerator {
|
|
|
33618
33681
|
setNoFollowExceptions(this.options.config.noFollowExceptions);
|
|
33619
33682
|
}
|
|
33620
33683
|
let tagDescriptions = {};
|
|
33621
|
-
const tagsTomlPath =
|
|
33684
|
+
const tagsTomlPath = path6.join(process.cwd(), "src", "tags.toml");
|
|
33622
33685
|
const tagsTomlFile = Bun.file(tagsTomlPath);
|
|
33623
33686
|
if (await tagsTomlFile.exists()) {
|
|
33624
33687
|
try {
|
|
@@ -33686,7 +33749,7 @@ class SiteGenerator {
|
|
|
33686
33749
|
const notFoundHtml = import_nunjucks.default.render("404.njk", {
|
|
33687
33750
|
site: this.options.config
|
|
33688
33751
|
});
|
|
33689
|
-
await Bun.write(
|
|
33752
|
+
await Bun.write(path6.join(this.options.outputDir, "404.html"), notFoundHtml);
|
|
33690
33753
|
console.log("Generated 404.html");
|
|
33691
33754
|
} catch (error) {
|
|
33692
33755
|
if (error instanceof Error && error.message.includes("404.njk")) {
|
|
@@ -33698,7 +33761,7 @@ class SiteGenerator {
|
|
|
33698
33761
|
}
|
|
33699
33762
|
async generateYearArchives() {
|
|
33700
33763
|
for (const [year, yearPosts] of Object.entries(this.site.postsByYear)) {
|
|
33701
|
-
const yearDir =
|
|
33764
|
+
const yearDir = path6.join(this.options.outputDir, year);
|
|
33702
33765
|
await ensureDir(yearDir);
|
|
33703
33766
|
const pageSize = 10;
|
|
33704
33767
|
const totalPages = Math.ceil(yearPosts.length / pageSize);
|
|
@@ -33737,11 +33800,11 @@ class SiteGenerator {
|
|
|
33737
33800
|
jsonLd
|
|
33738
33801
|
});
|
|
33739
33802
|
if (page === 1) {
|
|
33740
|
-
await Bun.write(
|
|
33803
|
+
await Bun.write(path6.join(yearDir, "index.html"), yearPageHtml);
|
|
33741
33804
|
} else {
|
|
33742
|
-
const pageDir =
|
|
33805
|
+
const pageDir = path6.join(yearDir, "page", page.toString());
|
|
33743
33806
|
await ensureDir(pageDir);
|
|
33744
|
-
await Bun.write(
|
|
33807
|
+
await Bun.write(path6.join(pageDir, "index.html"), yearPageHtml);
|
|
33745
33808
|
}
|
|
33746
33809
|
}
|
|
33747
33810
|
}
|
|
@@ -33771,18 +33834,18 @@ class SiteGenerator {
|
|
|
33771
33834
|
noindex: page > 2
|
|
33772
33835
|
});
|
|
33773
33836
|
if (page === 1) {
|
|
33774
|
-
await Bun.write(
|
|
33837
|
+
await Bun.write(path6.join(this.options.outputDir, "index.html"), pageHtml);
|
|
33775
33838
|
} else {
|
|
33776
|
-
const pageDir =
|
|
33839
|
+
const pageDir = path6.join(this.options.outputDir, "page", page.toString());
|
|
33777
33840
|
await ensureDir(pageDir);
|
|
33778
|
-
await Bun.write(
|
|
33841
|
+
await Bun.write(path6.join(pageDir, "index.html"), pageHtml);
|
|
33779
33842
|
}
|
|
33780
33843
|
}
|
|
33781
33844
|
}
|
|
33782
33845
|
async generatePostPages() {
|
|
33783
33846
|
for (const post of this.site.posts) {
|
|
33784
33847
|
const postPath = post.url.substring(1);
|
|
33785
|
-
const postDir =
|
|
33848
|
+
const postDir = path6.join(this.options.outputDir, postPath);
|
|
33786
33849
|
await ensureDir(postDir);
|
|
33787
33850
|
const imageUrl = extractFirstImageUrl(post.html, this.options.config.baseUrl);
|
|
33788
33851
|
const schemas = generatePostPageSchemas({
|
|
@@ -33797,19 +33860,19 @@ class SiteGenerator {
|
|
|
33797
33860
|
post,
|
|
33798
33861
|
jsonLd
|
|
33799
33862
|
});
|
|
33800
|
-
await Bun.write(
|
|
33863
|
+
await Bun.write(path6.join(postDir, "index.html"), postHtml);
|
|
33801
33864
|
}
|
|
33802
33865
|
}
|
|
33803
33866
|
async generateTagPages() {
|
|
33804
|
-
const tagsDir =
|
|
33867
|
+
const tagsDir = path6.join(this.options.outputDir, "tags");
|
|
33805
33868
|
await ensureDir(tagsDir);
|
|
33806
33869
|
const tagIndexHtml = import_nunjucks.default.render("tags.njk", {
|
|
33807
33870
|
site: this.options.config,
|
|
33808
33871
|
tags: this.getSortedTags()
|
|
33809
33872
|
});
|
|
33810
|
-
await Bun.write(
|
|
33873
|
+
await Bun.write(path6.join(tagsDir, "index.html"), tagIndexHtml);
|
|
33811
33874
|
for (const [tagName, tagData] of Object.entries(this.site.tags)) {
|
|
33812
|
-
const tagDir =
|
|
33875
|
+
const tagDir = path6.join(tagsDir, tagData.slug);
|
|
33813
33876
|
await ensureDir(tagDir);
|
|
33814
33877
|
const pageSize = 10;
|
|
33815
33878
|
const totalPages = Math.ceil(tagData.posts.length / pageSize);
|
|
@@ -33852,24 +33915,24 @@ class SiteGenerator {
|
|
|
33852
33915
|
jsonLd
|
|
33853
33916
|
});
|
|
33854
33917
|
if (page === 1) {
|
|
33855
|
-
await Bun.write(
|
|
33918
|
+
await Bun.write(path6.join(tagDir, "index.html"), tagPageHtml);
|
|
33856
33919
|
} else {
|
|
33857
|
-
const pageDir =
|
|
33920
|
+
const pageDir = path6.join(tagDir, "page", page.toString());
|
|
33858
33921
|
await ensureDir(pageDir);
|
|
33859
|
-
await Bun.write(
|
|
33922
|
+
await Bun.write(path6.join(pageDir, "index.html"), tagPageHtml);
|
|
33860
33923
|
}
|
|
33861
33924
|
}
|
|
33862
33925
|
}
|
|
33863
33926
|
}
|
|
33864
33927
|
async generateMapPage() {
|
|
33865
33928
|
try {
|
|
33866
|
-
const mapDir =
|
|
33929
|
+
const mapDir = path6.join(this.options.outputDir, "map");
|
|
33867
33930
|
await ensureDir(mapDir);
|
|
33868
33931
|
const mapHtml = import_nunjucks.default.render("map.njk", {
|
|
33869
33932
|
site: this.options.config,
|
|
33870
33933
|
posts: this.site.posts
|
|
33871
33934
|
});
|
|
33872
|
-
await Bun.write(
|
|
33935
|
+
await Bun.write(path6.join(mapDir, "index.html"), mapHtml);
|
|
33873
33936
|
console.log("Generated map page");
|
|
33874
33937
|
} catch (error) {
|
|
33875
33938
|
if (error instanceof Error && error.message.includes("map.njk")) {
|
|
@@ -33899,7 +33962,7 @@ class SiteGenerator {
|
|
|
33899
33962
|
}
|
|
33900
33963
|
}
|
|
33901
33964
|
async fallbackCSSGeneration(cssConfig) {
|
|
33902
|
-
const cssFilePath =
|
|
33965
|
+
const cssFilePath = path6.resolve(process.cwd(), cssConfig.input);
|
|
33903
33966
|
const cssFile = Bun.file(cssFilePath);
|
|
33904
33967
|
if (!await cssFile.exists()) {
|
|
33905
33968
|
console.warn(`CSS input file not found: ${cssFilePath}`);
|
|
@@ -33907,8 +33970,8 @@ class SiteGenerator {
|
|
|
33907
33970
|
}
|
|
33908
33971
|
try {
|
|
33909
33972
|
const cssContent = await cssFile.text();
|
|
33910
|
-
const outputPath =
|
|
33911
|
-
const outputDir =
|
|
33973
|
+
const outputPath = path6.resolve(this.options.outputDir, cssConfig.output);
|
|
33974
|
+
const outputDir = path6.dirname(outputPath);
|
|
33912
33975
|
await ensureDir(outputDir);
|
|
33913
33976
|
await Bun.write(outputPath, cssContent);
|
|
33914
33977
|
console.log("\u2705 CSS file copied successfully (fallback mode)");
|
|
@@ -33917,8 +33980,8 @@ class SiteGenerator {
|
|
|
33917
33980
|
}
|
|
33918
33981
|
}
|
|
33919
33982
|
async copyStaticAssets() {
|
|
33920
|
-
const assetsDir =
|
|
33921
|
-
const publicDir =
|
|
33983
|
+
const assetsDir = path6.join(this.options.templatesDir, "assets");
|
|
33984
|
+
const publicDir = path6.join(process.cwd(), "public");
|
|
33922
33985
|
async function dirExists(p) {
|
|
33923
33986
|
try {
|
|
33924
33987
|
const stat = await fs2.promises.stat(p);
|
|
@@ -33930,15 +33993,15 @@ class SiteGenerator {
|
|
|
33930
33993
|
const assetsDirFile = Bun.file(assetsDir);
|
|
33931
33994
|
if (await assetsDirFile.exists() && await dirExists(assetsDir)) {
|
|
33932
33995
|
const assetGlob = new Glob2("**/*.*");
|
|
33933
|
-
const assetsOutputDir =
|
|
33996
|
+
const assetsOutputDir = path6.join(this.options.outputDir, "assets");
|
|
33934
33997
|
await ensureDir(assetsOutputDir);
|
|
33935
33998
|
for await (const file of assetGlob.scan({
|
|
33936
33999
|
cwd: assetsDir,
|
|
33937
34000
|
absolute: true
|
|
33938
34001
|
})) {
|
|
33939
|
-
const relativePath =
|
|
33940
|
-
const targetPath =
|
|
33941
|
-
const targetDir =
|
|
34002
|
+
const relativePath = path6.relative(assetsDir, file);
|
|
34003
|
+
const targetPath = path6.join(assetsOutputDir, relativePath);
|
|
34004
|
+
const targetDir = path6.dirname(targetPath);
|
|
33942
34005
|
await ensureDir(targetDir);
|
|
33943
34006
|
await copyFile(file, targetPath);
|
|
33944
34007
|
}
|
|
@@ -33949,9 +34012,9 @@ class SiteGenerator {
|
|
|
33949
34012
|
withFileTypes: true
|
|
33950
34013
|
});
|
|
33951
34014
|
for (const entry of entries) {
|
|
33952
|
-
const srcPath =
|
|
33953
|
-
const relativePath =
|
|
33954
|
-
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);
|
|
33955
34018
|
if (!relativePath)
|
|
33956
34019
|
continue;
|
|
33957
34020
|
if (entry.isDirectory()) {
|
|
@@ -33960,7 +34023,7 @@ class SiteGenerator {
|
|
|
33960
34023
|
} else if (entry.isFile()) {
|
|
33961
34024
|
const targetFile = Bun.file(destPath);
|
|
33962
34025
|
if (!await targetFile.exists()) {
|
|
33963
|
-
const targetDir =
|
|
34026
|
+
const targetDir = path6.dirname(destPath);
|
|
33964
34027
|
await ensureDir(targetDir);
|
|
33965
34028
|
await copyFile(srcPath, destPath);
|
|
33966
34029
|
}
|
|
@@ -34060,7 +34123,7 @@ ${channelXml}
|
|
|
34060
34123
|
${rssItems}
|
|
34061
34124
|
</channel>
|
|
34062
34125
|
</rss>`;
|
|
34063
|
-
await Bun.write(
|
|
34126
|
+
await Bun.write(path6.join(this.options.outputDir, "feed.xml"), rssContent);
|
|
34064
34127
|
}
|
|
34065
34128
|
async generateSitemap() {
|
|
34066
34129
|
const currentDate = toPacificTime(new Date).toISOString();
|
|
@@ -34179,7 +34242,7 @@ ${rssItems}
|
|
|
34179
34242
|
}
|
|
34180
34243
|
}
|
|
34181
34244
|
sitemapContent += `</urlset>`;
|
|
34182
|
-
await Bun.write(
|
|
34245
|
+
await Bun.write(path6.join(this.options.outputDir, "sitemap.xml"), sitemapContent);
|
|
34183
34246
|
console.log("Generated sitemap.xml");
|
|
34184
34247
|
const urlCount = this.site.posts.length + Object.keys(this.site.tags).length + 10;
|
|
34185
34248
|
const sitemapSize = sitemapContent.length;
|
|
@@ -34199,7 +34262,7 @@ ${rssItems}
|
|
|
34199
34262
|
</sitemap>
|
|
34200
34263
|
`;
|
|
34201
34264
|
sitemapIndexContent += `</sitemapindex>`;
|
|
34202
|
-
await Bun.write(
|
|
34265
|
+
await Bun.write(path6.join(this.options.outputDir, "sitemap_index.xml"), sitemapIndexContent);
|
|
34203
34266
|
console.log("Generated sitemap_index.xml");
|
|
34204
34267
|
}
|
|
34205
34268
|
async generateRobotsTxt() {
|
|
@@ -34221,7 +34284,7 @@ Sitemap: ${config.baseUrl}/sitemap.xml
|
|
|
34221
34284
|
# Disallow: /admin/
|
|
34222
34285
|
# Disallow: /api/
|
|
34223
34286
|
`;
|
|
34224
|
-
await Bun.write(
|
|
34287
|
+
await Bun.write(path6.join(this.options.outputDir, "robots.txt"), robotsTxtContent);
|
|
34225
34288
|
console.log("Generated robots.txt");
|
|
34226
34289
|
}
|
|
34227
34290
|
}
|
|
@@ -34235,10 +34298,10 @@ var defaultDeps2 = {
|
|
|
34235
34298
|
};
|
|
34236
34299
|
async function handleGenerateCommand(options2, deps = defaultDeps2) {
|
|
34237
34300
|
try {
|
|
34238
|
-
const configPath =
|
|
34239
|
-
const contentDir =
|
|
34240
|
-
const outputDir =
|
|
34241
|
-
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);
|
|
34242
34305
|
deps.logger.log("Generating site with:");
|
|
34243
34306
|
deps.logger.log(`- Config file: ${configPath}`);
|
|
34244
34307
|
deps.logger.log(`- Content directory: ${contentDir}`);
|
|
@@ -34266,11 +34329,11 @@ function registerGenerateCommand(program2) {
|
|
|
34266
34329
|
}
|
|
34267
34330
|
|
|
34268
34331
|
// src/utils/image-uploader.ts
|
|
34269
|
-
import
|
|
34332
|
+
import path9 from "path";
|
|
34270
34333
|
|
|
34271
34334
|
// src/utils/s3-uploader.ts
|
|
34272
34335
|
var {S3Client } = globalThis.Bun;
|
|
34273
|
-
import
|
|
34336
|
+
import path8 from "path";
|
|
34274
34337
|
|
|
34275
34338
|
class S3Uploader {
|
|
34276
34339
|
s3Config;
|
|
@@ -34393,8 +34456,8 @@ class S3Uploader {
|
|
|
34393
34456
|
let failedCount = 0;
|
|
34394
34457
|
const uploadTasks = imageFiles.map((imageFile) => async () => {
|
|
34395
34458
|
try {
|
|
34396
|
-
const imagePath =
|
|
34397
|
-
const filename =
|
|
34459
|
+
const imagePath = path8.join(imagesDir, imageFile);
|
|
34460
|
+
const filename = path8.basename(imagePath);
|
|
34398
34461
|
const file = Bun.file(imagePath);
|
|
34399
34462
|
const contentType = file.type;
|
|
34400
34463
|
if (process.env.BUNKI_DRY_RUN === "true") {} else {
|
|
@@ -34428,10 +34491,10 @@ function createUploader(config) {
|
|
|
34428
34491
|
}
|
|
34429
34492
|
|
|
34430
34493
|
// src/utils/image-uploader.ts
|
|
34431
|
-
var DEFAULT_IMAGES_DIR =
|
|
34494
|
+
var DEFAULT_IMAGES_DIR = path9.join(process.cwd(), "assets");
|
|
34432
34495
|
async function uploadImages(options2 = {}) {
|
|
34433
34496
|
try {
|
|
34434
|
-
const imagesDir =
|
|
34497
|
+
const imagesDir = path9.resolve(options2.images || DEFAULT_IMAGES_DIR);
|
|
34435
34498
|
if (!await fileExists(imagesDir)) {
|
|
34436
34499
|
console.log(`Creating images directory at ${imagesDir}...`);
|
|
34437
34500
|
await ensureDir(imagesDir);
|
|
@@ -34468,7 +34531,7 @@ async function uploadImages(options2 = {}) {
|
|
|
34468
34531
|
const uploader = createUploader(s3Config);
|
|
34469
34532
|
const imageUrlMap = await uploader.uploadImages(imagesDir, options2.minYear);
|
|
34470
34533
|
if (options2.outputJson) {
|
|
34471
|
-
const outputFile =
|
|
34534
|
+
const outputFile = path9.resolve(options2.outputJson);
|
|
34472
34535
|
await Bun.write(outputFile, JSON.stringify(imageUrlMap, null, 2));
|
|
34473
34536
|
console.log(`Image URL mapping saved to ${outputFile}`);
|
|
34474
34537
|
}
|
|
@@ -34514,7 +34577,7 @@ function registerImagesPushCommand(program2) {
|
|
|
34514
34577
|
}
|
|
34515
34578
|
|
|
34516
34579
|
// src/cli/commands/init.ts
|
|
34517
|
-
import
|
|
34580
|
+
import path10 from "path";
|
|
34518
34581
|
var defaultDependencies = {
|
|
34519
34582
|
createDefaultConfig,
|
|
34520
34583
|
ensureDir,
|
|
@@ -34524,7 +34587,7 @@ var defaultDependencies = {
|
|
|
34524
34587
|
};
|
|
34525
34588
|
async function handleInitCommand(options2, deps = defaultDependencies) {
|
|
34526
34589
|
try {
|
|
34527
|
-
const configPath =
|
|
34590
|
+
const configPath = path10.resolve(options2.config);
|
|
34528
34591
|
const configCreated = await deps.createDefaultConfig(configPath);
|
|
34529
34592
|
if (!configCreated) {
|
|
34530
34593
|
deps.logger.log(`
|
|
@@ -34533,19 +34596,19 @@ Skipped initialization because the config file already exists`);
|
|
|
34533
34596
|
}
|
|
34534
34597
|
deps.logger.log("Creating directory structure...");
|
|
34535
34598
|
const baseDir = process.cwd();
|
|
34536
|
-
const contentDir =
|
|
34537
|
-
const templatesDir =
|
|
34538
|
-
const stylesDir =
|
|
34539
|
-
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");
|
|
34540
34603
|
await deps.ensureDir(contentDir);
|
|
34541
34604
|
await deps.ensureDir(templatesDir);
|
|
34542
34605
|
await deps.ensureDir(stylesDir);
|
|
34543
34606
|
await deps.ensureDir(publicDir);
|
|
34544
34607
|
for (const [filename, content] of Object.entries(getDefaultTemplates())) {
|
|
34545
|
-
await deps.writeFile(
|
|
34608
|
+
await deps.writeFile(path10.join(templatesDir, filename), content);
|
|
34546
34609
|
}
|
|
34547
|
-
await deps.writeFile(
|
|
34548
|
-
await deps.writeFile(
|
|
34610
|
+
await deps.writeFile(path10.join(stylesDir, "main.css"), getDefaultCss());
|
|
34611
|
+
await deps.writeFile(path10.join(contentDir, "welcome.md"), getSamplePost());
|
|
34549
34612
|
deps.logger.log(`
|
|
34550
34613
|
Initialization complete! Here are the next steps:`);
|
|
34551
34614
|
deps.logger.log("1. Edit bunki.config.ts to configure your site");
|
|
@@ -35037,7 +35100,7 @@ function hello() {
|
|
|
35037
35100
|
}
|
|
35038
35101
|
|
|
35039
35102
|
// src/cli/commands/new-post.ts
|
|
35040
|
-
import
|
|
35103
|
+
import path11 from "path";
|
|
35041
35104
|
var defaultDeps4 = {
|
|
35042
35105
|
writeFile: (filePath, data) => Bun.write(filePath, data),
|
|
35043
35106
|
now: () => new Date,
|
|
@@ -35061,7 +35124,7 @@ async function handleNewCommand(title, options2, deps = defaultDeps4) {
|
|
|
35061
35124
|
` + `# ${title}
|
|
35062
35125
|
|
|
35063
35126
|
`;
|
|
35064
|
-
const filePath =
|
|
35127
|
+
const filePath = path11.join(DEFAULT_CONTENT_DIR, `${slug}.md`);
|
|
35065
35128
|
await deps.writeFile(filePath, frontmatter);
|
|
35066
35129
|
deps.logger.log(`Created new post: ${filePath}`);
|
|
35067
35130
|
return filePath;
|
|
@@ -35078,11 +35141,11 @@ function registerNewCommand(program2) {
|
|
|
35078
35141
|
}
|
|
35079
35142
|
|
|
35080
35143
|
// src/cli/commands/serve.ts
|
|
35081
|
-
import
|
|
35144
|
+
import path13 from "path";
|
|
35082
35145
|
|
|
35083
35146
|
// src/server.ts
|
|
35084
35147
|
import fs3 from "fs";
|
|
35085
|
-
import
|
|
35148
|
+
import path12 from "path";
|
|
35086
35149
|
async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
35087
35150
|
try {
|
|
35088
35151
|
const stats = await fs3.promises.stat(outputDir);
|
|
@@ -35117,18 +35180,18 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
|
35117
35180
|
let filePath = "";
|
|
35118
35181
|
if (homePaginationMatch) {
|
|
35119
35182
|
const pageNumber = homePaginationMatch[1];
|
|
35120
|
-
filePath =
|
|
35183
|
+
filePath = path12.join(outputDir, "page", pageNumber, "index.html");
|
|
35121
35184
|
} else if (tagPaginationMatch) {
|
|
35122
35185
|
const tagSlug = tagPaginationMatch[1];
|
|
35123
35186
|
const pageNumber = tagPaginationMatch[2];
|
|
35124
|
-
filePath =
|
|
35187
|
+
filePath = path12.join(outputDir, "tags", tagSlug, "page", pageNumber, "index.html");
|
|
35125
35188
|
} else if (yearPaginationMatch) {
|
|
35126
35189
|
const year = yearPaginationMatch[1];
|
|
35127
35190
|
const pageNumber = yearPaginationMatch[2];
|
|
35128
|
-
filePath =
|
|
35191
|
+
filePath = path12.join(outputDir, year, "page", pageNumber, "index.html");
|
|
35129
35192
|
} else {
|
|
35130
|
-
const directPath =
|
|
35131
|
-
const withoutSlash =
|
|
35193
|
+
const directPath = path12.join(outputDir, pathname);
|
|
35194
|
+
const withoutSlash = path12.join(outputDir, pathname + ".html");
|
|
35132
35195
|
const withHtml = pathname.endsWith(".html") ? directPath : withoutSlash;
|
|
35133
35196
|
const bunFileDirect = Bun.file(directPath);
|
|
35134
35197
|
const bunFileHtml = Bun.file(withHtml);
|
|
@@ -35137,7 +35200,7 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
|
35137
35200
|
} else if (await bunFileHtml.exists()) {
|
|
35138
35201
|
filePath = withHtml;
|
|
35139
35202
|
} else {
|
|
35140
|
-
const indexPath =
|
|
35203
|
+
const indexPath = path12.join(outputDir, pathname, "index.html");
|
|
35141
35204
|
const bunFileIndex = Bun.file(indexPath);
|
|
35142
35205
|
if (await bunFileIndex.exists()) {
|
|
35143
35206
|
filePath = indexPath;
|
|
@@ -35151,7 +35214,7 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
|
35151
35214
|
}
|
|
35152
35215
|
}
|
|
35153
35216
|
console.log(`Serving file: ${filePath}`);
|
|
35154
|
-
const extname =
|
|
35217
|
+
const extname = path12.extname(filePath);
|
|
35155
35218
|
let contentType = "text/html";
|
|
35156
35219
|
switch (extname) {
|
|
35157
35220
|
case ".js":
|
|
@@ -35210,7 +35273,7 @@ var defaultDeps5 = {
|
|
|
35210
35273
|
};
|
|
35211
35274
|
async function handleServeCommand(options2, deps = defaultDeps5) {
|
|
35212
35275
|
try {
|
|
35213
|
-
const outputDir =
|
|
35276
|
+
const outputDir = path13.resolve(options2.output);
|
|
35214
35277
|
const port = parseInt(options2.port, 10);
|
|
35215
35278
|
await deps.startServer(outputDir, port);
|
|
35216
35279
|
} catch (error) {
|