bunki 0.17.0 → 0.18.0
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 +144 -8
- package/dist/cli/commands/generate.d.ts +1 -0
- package/dist/cli.js +728 -499
- package/dist/index.js +288 -45
- package/dist/parser.d.ts +8 -0
- package/dist/site-generator.d.ts +10 -0
- package/dist/utils/build-cache.d.ts +76 -0
- package/dist/utils/change-detector.d.ts +40 -0
- package/package.json +1 -1
- package/dist/cli/commands/templates/archive-njk.d.ts +0 -4
- package/dist/cli/commands/templates/base-njk.d.ts +0 -4
- package/dist/cli/commands/templates/default-css.d.ts +0 -4
- package/dist/cli/commands/templates/index-njk.d.ts +0 -4
- package/dist/cli/commands/templates/index.d.ts +0 -14
- package/dist/cli/commands/templates/post-njk.d.ts +0 -4
- package/dist/cli/commands/templates/sample-post.d.ts +0 -4
- package/dist/cli/commands/templates/tag-njk.d.ts +0 -4
- package/dist/cli/commands/templates/tags-njk.d.ts +0 -4
package/dist/cli.js
CHANGED
|
@@ -28478,12 +28478,12 @@ function registerCssCommand(program2) {
|
|
|
28478
28478
|
}
|
|
28479
28479
|
|
|
28480
28480
|
// src/cli/commands/generate.ts
|
|
28481
|
-
import
|
|
28481
|
+
import path10 from "path";
|
|
28482
28482
|
|
|
28483
28483
|
// src/site-generator.ts
|
|
28484
28484
|
var import_nunjucks2 = __toESM(require_nunjucks(), 1);
|
|
28485
28485
|
var import_slugify = __toESM(require_slugify(), 1);
|
|
28486
|
-
import
|
|
28486
|
+
import path9 from "path";
|
|
28487
28487
|
|
|
28488
28488
|
// src/parser.ts
|
|
28489
28489
|
import path5 from "path";
|
|
@@ -33368,6 +33368,20 @@ function detectFileConflicts(files) {
|
|
|
33368
33368
|
}
|
|
33369
33369
|
return errors;
|
|
33370
33370
|
}
|
|
33371
|
+
async function parseMarkdownFiles(filePaths, cdnConfig) {
|
|
33372
|
+
const resultsPromises = filePaths.map((filePath) => parseMarkdownFile(filePath, cdnConfig).then((result) => ({
|
|
33373
|
+
result,
|
|
33374
|
+
filePath
|
|
33375
|
+
})));
|
|
33376
|
+
const results = await Promise.all(resultsPromises);
|
|
33377
|
+
const postsWithPaths = [];
|
|
33378
|
+
for (const { result, filePath } of results) {
|
|
33379
|
+
if (result.post) {
|
|
33380
|
+
postsWithPaths.push({ post: result.post, filePath });
|
|
33381
|
+
}
|
|
33382
|
+
}
|
|
33383
|
+
return postsWithPaths;
|
|
33384
|
+
}
|
|
33371
33385
|
async function parseMarkdownDirectory(contentDir, strictMode = false, cdnConfig) {
|
|
33372
33386
|
try {
|
|
33373
33387
|
const markdownFiles = await findFilesByPattern("**/*.md", contentDir, true);
|
|
@@ -33638,6 +33652,165 @@ function generateHomePageSchemas(options2) {
|
|
|
33638
33652
|
return schemas;
|
|
33639
33653
|
}
|
|
33640
33654
|
|
|
33655
|
+
// src/utils/build-cache.ts
|
|
33656
|
+
var {hash: hash2 } = globalThis.Bun;
|
|
33657
|
+
import path6 from "path";
|
|
33658
|
+
var CACHE_VERSION = "2.0.0";
|
|
33659
|
+
var CACHE_FILENAME = ".bunki-cache.json";
|
|
33660
|
+
async function hashFile(filePath) {
|
|
33661
|
+
try {
|
|
33662
|
+
const file = Bun.file(filePath);
|
|
33663
|
+
const content = await file.arrayBuffer();
|
|
33664
|
+
return hash2(content).toString(36);
|
|
33665
|
+
} catch (error) {
|
|
33666
|
+
return "";
|
|
33667
|
+
}
|
|
33668
|
+
}
|
|
33669
|
+
async function getFileMtime(filePath) {
|
|
33670
|
+
try {
|
|
33671
|
+
const stat = await Bun.file(filePath).stat();
|
|
33672
|
+
return stat?.mtime?.getTime() || 0;
|
|
33673
|
+
} catch (error) {
|
|
33674
|
+
return 0;
|
|
33675
|
+
}
|
|
33676
|
+
}
|
|
33677
|
+
async function loadCache(cwd) {
|
|
33678
|
+
const cachePath = path6.join(cwd, CACHE_FILENAME);
|
|
33679
|
+
const cacheFile = Bun.file(cachePath);
|
|
33680
|
+
try {
|
|
33681
|
+
if (await cacheFile.exists()) {
|
|
33682
|
+
const content = await cacheFile.text();
|
|
33683
|
+
const cache = JSON.parse(content);
|
|
33684
|
+
if (cache.version !== CACHE_VERSION) {
|
|
33685
|
+
console.log(`Cache version mismatch (${cache.version} vs ${CACHE_VERSION}), rebuilding...`);
|
|
33686
|
+
return createEmptyCache();
|
|
33687
|
+
}
|
|
33688
|
+
return cache;
|
|
33689
|
+
}
|
|
33690
|
+
} catch (error) {
|
|
33691
|
+
console.warn("Error loading cache, rebuilding:", error);
|
|
33692
|
+
}
|
|
33693
|
+
return createEmptyCache();
|
|
33694
|
+
}
|
|
33695
|
+
async function saveCache(cwd, cache) {
|
|
33696
|
+
const cachePath = path6.join(cwd, CACHE_FILENAME);
|
|
33697
|
+
try {
|
|
33698
|
+
await Bun.write(cachePath, JSON.stringify(cache, null, 2));
|
|
33699
|
+
} catch (error) {
|
|
33700
|
+
console.warn("Error saving cache:", error);
|
|
33701
|
+
}
|
|
33702
|
+
}
|
|
33703
|
+
function createEmptyCache() {
|
|
33704
|
+
return {
|
|
33705
|
+
version: CACHE_VERSION,
|
|
33706
|
+
files: {}
|
|
33707
|
+
};
|
|
33708
|
+
}
|
|
33709
|
+
async function hasFileChanged(filePath, cache) {
|
|
33710
|
+
const cached = cache.files[filePath];
|
|
33711
|
+
if (!cached) {
|
|
33712
|
+
return true;
|
|
33713
|
+
}
|
|
33714
|
+
const currentMtime = await getFileMtime(filePath);
|
|
33715
|
+
if (currentMtime !== cached.mtime) {
|
|
33716
|
+
const currentHash = await hashFile(filePath);
|
|
33717
|
+
return currentHash !== cached.hash;
|
|
33718
|
+
}
|
|
33719
|
+
return false;
|
|
33720
|
+
}
|
|
33721
|
+
async function updateCacheEntry(filePath, cache, options2) {
|
|
33722
|
+
const currentHash = await hashFile(filePath);
|
|
33723
|
+
const currentMtime = await getFileMtime(filePath);
|
|
33724
|
+
cache.files[filePath] = {
|
|
33725
|
+
hash: currentHash,
|
|
33726
|
+
mtime: currentMtime,
|
|
33727
|
+
post: options2?.post,
|
|
33728
|
+
outputs: options2?.outputs
|
|
33729
|
+
};
|
|
33730
|
+
}
|
|
33731
|
+
async function hasConfigChanged(configPath, cache) {
|
|
33732
|
+
const currentHash = await hashFile(configPath);
|
|
33733
|
+
if (!cache.configHash) {
|
|
33734
|
+
cache.configHash = currentHash;
|
|
33735
|
+
return true;
|
|
33736
|
+
}
|
|
33737
|
+
if (currentHash !== cache.configHash) {
|
|
33738
|
+
cache.configHash = currentHash;
|
|
33739
|
+
return true;
|
|
33740
|
+
}
|
|
33741
|
+
return false;
|
|
33742
|
+
}
|
|
33743
|
+
function loadCachedPosts(cache, filePaths) {
|
|
33744
|
+
const posts = [];
|
|
33745
|
+
for (const filePath of filePaths) {
|
|
33746
|
+
const entry = cache.files[filePath];
|
|
33747
|
+
if (entry?.post) {
|
|
33748
|
+
posts.push(entry.post);
|
|
33749
|
+
}
|
|
33750
|
+
}
|
|
33751
|
+
return posts;
|
|
33752
|
+
}
|
|
33753
|
+
|
|
33754
|
+
// src/utils/change-detector.ts
|
|
33755
|
+
async function detectChanges(currentFiles, cache, options2 = {}) {
|
|
33756
|
+
const changes = {
|
|
33757
|
+
changedPosts: [],
|
|
33758
|
+
deletedPosts: [],
|
|
33759
|
+
stylesChanged: false,
|
|
33760
|
+
configChanged: false,
|
|
33761
|
+
templatesChanged: false,
|
|
33762
|
+
fullRebuild: false
|
|
33763
|
+
};
|
|
33764
|
+
if (options2.configPath) {
|
|
33765
|
+
const configChanged = await hasFileChanged(options2.configPath, cache);
|
|
33766
|
+
if (configChanged) {
|
|
33767
|
+
changes.configChanged = true;
|
|
33768
|
+
changes.fullRebuild = true;
|
|
33769
|
+
return changes;
|
|
33770
|
+
}
|
|
33771
|
+
}
|
|
33772
|
+
if (options2.templatePaths && options2.templatePaths.length > 0) {
|
|
33773
|
+
for (const templatePath of options2.templatePaths) {
|
|
33774
|
+
const changed = await hasFileChanged(templatePath, cache);
|
|
33775
|
+
if (changed) {
|
|
33776
|
+
changes.templatesChanged = true;
|
|
33777
|
+
changes.fullRebuild = true;
|
|
33778
|
+
return changes;
|
|
33779
|
+
}
|
|
33780
|
+
}
|
|
33781
|
+
}
|
|
33782
|
+
if (options2.stylesPaths && options2.stylesPaths.length > 0) {
|
|
33783
|
+
for (const stylePath of options2.stylesPaths) {
|
|
33784
|
+
const changed = await hasFileChanged(stylePath, cache);
|
|
33785
|
+
if (changed) {
|
|
33786
|
+
changes.stylesChanged = true;
|
|
33787
|
+
break;
|
|
33788
|
+
}
|
|
33789
|
+
}
|
|
33790
|
+
}
|
|
33791
|
+
for (const filePath of currentFiles) {
|
|
33792
|
+
const changed = await hasFileChanged(filePath, cache);
|
|
33793
|
+
if (changed) {
|
|
33794
|
+
changes.changedPosts.push(filePath);
|
|
33795
|
+
}
|
|
33796
|
+
}
|
|
33797
|
+
const cachedFiles = Object.keys(cache.files).filter((f) => f.endsWith(".md"));
|
|
33798
|
+
for (const cachedFile of cachedFiles) {
|
|
33799
|
+
if (!currentFiles.includes(cachedFile)) {
|
|
33800
|
+
changes.deletedPosts.push(cachedFile);
|
|
33801
|
+
}
|
|
33802
|
+
}
|
|
33803
|
+
if (changes.deletedPosts.length > 0) {
|
|
33804
|
+
changes.fullRebuild = true;
|
|
33805
|
+
}
|
|
33806
|
+
return changes;
|
|
33807
|
+
}
|
|
33808
|
+
function estimateTimeSaved(totalPosts, changedPosts) {
|
|
33809
|
+
const avgTimePerPost = 6;
|
|
33810
|
+
const skippedPosts = totalPosts - changedPosts;
|
|
33811
|
+
return skippedPosts * avgTimePerPost;
|
|
33812
|
+
}
|
|
33813
|
+
|
|
33641
33814
|
// src/utils/xml-builder.ts
|
|
33642
33815
|
function escapeXml(text) {
|
|
33643
33816
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -33870,14 +34043,14 @@ Sitemap: ${config.baseUrl}/sitemap.xml
|
|
|
33870
34043
|
|
|
33871
34044
|
// src/generators/pages.ts
|
|
33872
34045
|
var import_nunjucks = __toESM(require_nunjucks(), 1);
|
|
33873
|
-
import
|
|
34046
|
+
import path7 from "path";
|
|
33874
34047
|
function getSortedTags(tags, limit) {
|
|
33875
34048
|
const sorted = Object.values(tags).sort((a, b2) => b2.count - a.count);
|
|
33876
34049
|
return limit ? sorted.slice(0, limit) : sorted;
|
|
33877
34050
|
}
|
|
33878
34051
|
async function writeHtmlFile(outputDir, relativePath, content) {
|
|
33879
|
-
const fullPath =
|
|
33880
|
-
const dir =
|
|
34052
|
+
const fullPath = path7.join(outputDir, relativePath);
|
|
34053
|
+
const dir = path7.dirname(fullPath);
|
|
33881
34054
|
await ensureDir(dir);
|
|
33882
34055
|
await Bun.write(fullPath, content);
|
|
33883
34056
|
}
|
|
@@ -34053,7 +34226,7 @@ async function generateMapPage(site, config, outputDir) {
|
|
|
34053
34226
|
|
|
34054
34227
|
// src/generators/assets.ts
|
|
34055
34228
|
var {Glob: Glob2 } = globalThis.Bun;
|
|
34056
|
-
import
|
|
34229
|
+
import path8 from "path";
|
|
34057
34230
|
async function generateStylesheet(config, outputDir) {
|
|
34058
34231
|
const cssConfig = config.css || getDefaultCSSConfig();
|
|
34059
34232
|
if (!cssConfig.enabled) {
|
|
@@ -34074,15 +34247,15 @@ async function generateStylesheet(config, outputDir) {
|
|
|
34074
34247
|
}
|
|
34075
34248
|
}
|
|
34076
34249
|
async function fallbackCSSGeneration(cssConfig, outputDir) {
|
|
34077
|
-
const cssFilePath =
|
|
34250
|
+
const cssFilePath = path8.resolve(process.cwd(), cssConfig.input);
|
|
34078
34251
|
const cssFile = Bun.file(cssFilePath);
|
|
34079
34252
|
if (!await cssFile.exists()) {
|
|
34080
34253
|
console.warn(`CSS input file not found: ${cssFilePath}`);
|
|
34081
34254
|
return;
|
|
34082
34255
|
}
|
|
34083
34256
|
try {
|
|
34084
|
-
const outputPath =
|
|
34085
|
-
const outputDirPath =
|
|
34257
|
+
const outputPath = path8.resolve(outputDir, cssConfig.output);
|
|
34258
|
+
const outputDirPath = path8.dirname(outputPath);
|
|
34086
34259
|
await ensureDir(outputDirPath);
|
|
34087
34260
|
await Bun.write(outputPath, cssFile);
|
|
34088
34261
|
console.log("\u2705 CSS file copied successfully (fallback mode)");
|
|
@@ -34091,19 +34264,19 @@ async function fallbackCSSGeneration(cssConfig, outputDir) {
|
|
|
34091
34264
|
}
|
|
34092
34265
|
}
|
|
34093
34266
|
async function copyStaticAssets(templatesDir, outputDir) {
|
|
34094
|
-
const assetsDir =
|
|
34095
|
-
const publicDir =
|
|
34267
|
+
const assetsDir = path8.join(templatesDir, "assets");
|
|
34268
|
+
const publicDir = path8.join(process.cwd(), "public");
|
|
34096
34269
|
if (await isDirectory(assetsDir)) {
|
|
34097
34270
|
const assetGlob = new Glob2("**/*.*");
|
|
34098
|
-
const assetsOutputDir =
|
|
34271
|
+
const assetsOutputDir = path8.join(outputDir, "assets");
|
|
34099
34272
|
await ensureDir(assetsOutputDir);
|
|
34100
34273
|
for await (const file of assetGlob.scan({
|
|
34101
34274
|
cwd: assetsDir,
|
|
34102
34275
|
absolute: true
|
|
34103
34276
|
})) {
|
|
34104
|
-
const relativePath =
|
|
34105
|
-
const targetPath =
|
|
34106
|
-
const targetDir =
|
|
34277
|
+
const relativePath = path8.relative(assetsDir, file);
|
|
34278
|
+
const targetPath = path8.join(assetsOutputDir, relativePath);
|
|
34279
|
+
const targetDir = path8.dirname(targetPath);
|
|
34107
34280
|
await ensureDir(targetDir);
|
|
34108
34281
|
await copyFile(file, targetPath);
|
|
34109
34282
|
}
|
|
@@ -34117,9 +34290,9 @@ async function copyStaticAssets(templatesDir, outputDir) {
|
|
|
34117
34290
|
})) {
|
|
34118
34291
|
if (await isDirectory(file))
|
|
34119
34292
|
continue;
|
|
34120
|
-
const relativePath =
|
|
34121
|
-
const destPath =
|
|
34122
|
-
const targetDir =
|
|
34293
|
+
const relativePath = path8.relative(publicDir, file);
|
|
34294
|
+
const destPath = path8.join(outputDir, relativePath);
|
|
34295
|
+
const targetDir = path8.dirname(destPath);
|
|
34123
34296
|
await ensureDir(targetDir);
|
|
34124
34297
|
await copyFile(file, destPath);
|
|
34125
34298
|
}
|
|
@@ -34201,6 +34374,8 @@ class SiteGenerator {
|
|
|
34201
34374
|
options;
|
|
34202
34375
|
site;
|
|
34203
34376
|
metrics;
|
|
34377
|
+
cache = null;
|
|
34378
|
+
incrementalMode = false;
|
|
34204
34379
|
constructor(options2) {
|
|
34205
34380
|
this.options = options2;
|
|
34206
34381
|
this.site = {
|
|
@@ -34212,8 +34387,7 @@ class SiteGenerator {
|
|
|
34212
34387
|
this.metrics = new MetricsCollector;
|
|
34213
34388
|
const env = import_nunjucks2.default.configure(this.options.templatesDir, {
|
|
34214
34389
|
autoescape: true,
|
|
34215
|
-
watch: false
|
|
34216
|
-
noCache: false
|
|
34390
|
+
watch: false
|
|
34217
34391
|
});
|
|
34218
34392
|
env.addFilter("date", (date, format) => {
|
|
34219
34393
|
const d2 = toPacificTime(date);
|
|
@@ -34246,6 +34420,9 @@ class SiteGenerator {
|
|
|
34246
34420
|
}
|
|
34247
34421
|
});
|
|
34248
34422
|
}
|
|
34423
|
+
enableIncrementalMode() {
|
|
34424
|
+
this.incrementalMode = true;
|
|
34425
|
+
}
|
|
34249
34426
|
async initialize() {
|
|
34250
34427
|
this.metrics.startStage("initialization");
|
|
34251
34428
|
console.log("Initializing site generator...");
|
|
@@ -34254,7 +34431,7 @@ class SiteGenerator {
|
|
|
34254
34431
|
setNoFollowExceptions(this.options.config.noFollowExceptions);
|
|
34255
34432
|
}
|
|
34256
34433
|
let tagDescriptions = {};
|
|
34257
|
-
const tagsTomlPath =
|
|
34434
|
+
const tagsTomlPath = path9.join(process.cwd(), "src", "tags.toml");
|
|
34258
34435
|
const tagsTomlFile = Bun.file(tagsTomlPath);
|
|
34259
34436
|
if (await tagsTomlFile.exists()) {
|
|
34260
34437
|
try {
|
|
@@ -34264,8 +34441,10 @@ class SiteGenerator {
|
|
|
34264
34441
|
console.warn("Error loading tag descriptions:", error);
|
|
34265
34442
|
}
|
|
34266
34443
|
}
|
|
34267
|
-
|
|
34268
|
-
|
|
34444
|
+
if (this.incrementalMode) {
|
|
34445
|
+
this.cache = await loadCache(process.cwd());
|
|
34446
|
+
}
|
|
34447
|
+
const posts = await this.parseContent();
|
|
34269
34448
|
const tags = {};
|
|
34270
34449
|
posts.forEach((post) => {
|
|
34271
34450
|
post.tagSlugs = {};
|
|
@@ -34303,7 +34482,21 @@ class SiteGenerator {
|
|
|
34303
34482
|
console.log("Generating static site...");
|
|
34304
34483
|
await ensureDir(this.options.outputDir);
|
|
34305
34484
|
this.metrics.startStage("cssProcessing");
|
|
34306
|
-
|
|
34485
|
+
let cssChanged = true;
|
|
34486
|
+
if (this.cache && this.incrementalMode && this.options.config.css) {
|
|
34487
|
+
const cssInputPath = path9.resolve(process.cwd(), this.options.config.css.input);
|
|
34488
|
+
const cssOutputPath = path9.join(this.options.outputDir, this.options.config.css.output);
|
|
34489
|
+
const cssOutputExists = await Bun.file(cssOutputPath).exists();
|
|
34490
|
+
cssChanged = await hasFileChanged(cssInputPath, this.cache);
|
|
34491
|
+
if (!cssChanged && cssOutputExists) {
|
|
34492
|
+
console.log("\u23ED\uFE0F Skipping CSS (unchanged)");
|
|
34493
|
+
} else {
|
|
34494
|
+
await generateStylesheet(this.options.config, this.options.outputDir);
|
|
34495
|
+
await updateCacheEntry(cssInputPath, this.cache);
|
|
34496
|
+
}
|
|
34497
|
+
} else {
|
|
34498
|
+
await generateStylesheet(this.options.config, this.options.outputDir);
|
|
34499
|
+
}
|
|
34307
34500
|
this.metrics.startStage("pageGeneration");
|
|
34308
34501
|
await Promise.all([
|
|
34309
34502
|
generateIndexPages(this.site, this.options.config, this.options.outputDir),
|
|
@@ -34320,25 +34513,75 @@ class SiteGenerator {
|
|
|
34320
34513
|
const outputStats = await this.calculateOutputStats();
|
|
34321
34514
|
const buildMetrics = this.metrics.getMetrics(outputStats);
|
|
34322
34515
|
displayMetrics(buildMetrics);
|
|
34516
|
+
if (this.cache) {
|
|
34517
|
+
await saveCache(process.cwd(), this.cache);
|
|
34518
|
+
}
|
|
34323
34519
|
}
|
|
34324
34520
|
async generateFeeds() {
|
|
34325
34521
|
const pageSize = 10;
|
|
34326
34522
|
const rssContent = generateRSSFeed(this.site, this.options.config);
|
|
34327
|
-
await Bun.write(
|
|
34523
|
+
await Bun.write(path9.join(this.options.outputDir, "feed.xml"), rssContent);
|
|
34328
34524
|
const sitemapContent = generateSitemap(this.site, this.options.config, pageSize);
|
|
34329
|
-
await Bun.write(
|
|
34525
|
+
await Bun.write(path9.join(this.options.outputDir, "sitemap.xml"), sitemapContent);
|
|
34330
34526
|
console.log("Generated sitemap.xml");
|
|
34331
34527
|
const urlCount = this.site.posts.length + Object.keys(this.site.tags).length + 10;
|
|
34332
34528
|
const sitemapSize = sitemapContent.length;
|
|
34333
34529
|
if (urlCount > 1000 || sitemapSize > 40000) {
|
|
34334
34530
|
const sitemapIndexContent = generateSitemapIndex(this.options.config);
|
|
34335
|
-
await Bun.write(
|
|
34531
|
+
await Bun.write(path9.join(this.options.outputDir, "sitemap_index.xml"), sitemapIndexContent);
|
|
34336
34532
|
console.log("Generated sitemap_index.xml");
|
|
34337
34533
|
}
|
|
34338
34534
|
const robotsTxtContent = generateRobotsTxt(this.options.config);
|
|
34339
|
-
await Bun.write(
|
|
34535
|
+
await Bun.write(path9.join(this.options.outputDir, "robots.txt"), robotsTxtContent);
|
|
34340
34536
|
console.log("Generated robots.txt");
|
|
34341
34537
|
}
|
|
34538
|
+
async parseContent() {
|
|
34539
|
+
const strictMode = this.options.config.strictMode ?? false;
|
|
34540
|
+
if (!this.incrementalMode || !this.cache) {
|
|
34541
|
+
const posts = await parseMarkdownDirectory(this.options.contentDir, strictMode, this.options.config.cdn);
|
|
34542
|
+
if (this.cache) {
|
|
34543
|
+
const allFiles2 = await findFilesByPattern("**/*.md", this.options.contentDir, true);
|
|
34544
|
+
const postsByPath = new Map(posts.map((p) => [p.url, p]));
|
|
34545
|
+
for (let i = 0;i < allFiles2.length; i++) {
|
|
34546
|
+
const filePath = allFiles2[i];
|
|
34547
|
+
const post = posts[i];
|
|
34548
|
+
await updateCacheEntry(filePath, this.cache, { post });
|
|
34549
|
+
}
|
|
34550
|
+
}
|
|
34551
|
+
return posts;
|
|
34552
|
+
}
|
|
34553
|
+
const allFiles = await findFilesByPattern("**/*.md", this.options.contentDir, true);
|
|
34554
|
+
const configPath = path9.join(process.cwd(), "bunki.config.ts");
|
|
34555
|
+
const configChanged = await hasConfigChanged(configPath, this.cache);
|
|
34556
|
+
if (configChanged) {
|
|
34557
|
+
console.log("Config changed, full rebuild required");
|
|
34558
|
+
return this.parseContent();
|
|
34559
|
+
}
|
|
34560
|
+
const changes = await detectChanges(allFiles, this.cache);
|
|
34561
|
+
if (changes.fullRebuild) {
|
|
34562
|
+
console.log("Full rebuild required");
|
|
34563
|
+
this.incrementalMode = false;
|
|
34564
|
+
return this.parseContent();
|
|
34565
|
+
}
|
|
34566
|
+
if (changes.changedPosts.length === 0) {
|
|
34567
|
+
console.log("No content changes detected, using cached posts");
|
|
34568
|
+
const cachedPosts2 = loadCachedPosts(this.cache, allFiles);
|
|
34569
|
+
console.log(`\u2728 Loaded ${cachedPosts2.length} posts from cache (0ms parsing)`);
|
|
34570
|
+
return cachedPosts2;
|
|
34571
|
+
}
|
|
34572
|
+
const timeSaved = estimateTimeSaved(allFiles.length, changes.changedPosts.length);
|
|
34573
|
+
console.log(`\uD83D\uDCE6 Incremental build: ${changes.changedPosts.length}/${allFiles.length} files changed (~${timeSaved}ms saved)`);
|
|
34574
|
+
const changedPostsWithPaths = await parseMarkdownFiles(changes.changedPosts, this.options.config.cdn);
|
|
34575
|
+
const unchangedFiles = allFiles.filter((f) => !changes.changedPosts.includes(f));
|
|
34576
|
+
const cachedPosts = loadCachedPosts(this.cache, unchangedFiles);
|
|
34577
|
+
console.log(` Parsed: ${changedPostsWithPaths.length} new/changed, loaded: ${cachedPosts.length} from cache`);
|
|
34578
|
+
const changedPosts = changedPostsWithPaths.map((p) => p.post);
|
|
34579
|
+
const allPosts = [...changedPosts, ...cachedPosts].sort((a, b2) => new Date(b2.date).getTime() - new Date(a.date).getTime());
|
|
34580
|
+
for (const { post, filePath } of changedPostsWithPaths) {
|
|
34581
|
+
await updateCacheEntry(filePath, this.cache, { post });
|
|
34582
|
+
}
|
|
34583
|
+
return allPosts;
|
|
34584
|
+
}
|
|
34342
34585
|
groupPostsByYear(posts) {
|
|
34343
34586
|
const postsByYear = {};
|
|
34344
34587
|
for (const post of posts) {
|
|
@@ -34387,15 +34630,18 @@ var defaultDeps2 = {
|
|
|
34387
34630
|
};
|
|
34388
34631
|
async function handleGenerateCommand(options2, deps = defaultDeps2) {
|
|
34389
34632
|
try {
|
|
34390
|
-
const configPath =
|
|
34391
|
-
const contentDir =
|
|
34392
|
-
const outputDir =
|
|
34393
|
-
const templatesDir =
|
|
34633
|
+
const configPath = path10.resolve(options2.config);
|
|
34634
|
+
const contentDir = path10.resolve(options2.content);
|
|
34635
|
+
const outputDir = path10.resolve(options2.output);
|
|
34636
|
+
const templatesDir = path10.resolve(options2.templates);
|
|
34394
34637
|
deps.logger.log("Generating site with:");
|
|
34395
34638
|
deps.logger.log(`- Config file: ${configPath}`);
|
|
34396
34639
|
deps.logger.log(`- Content directory: ${contentDir}`);
|
|
34397
34640
|
deps.logger.log(`- Output directory: ${outputDir}`);
|
|
34398
34641
|
deps.logger.log(`- Templates directory: ${templatesDir}`);
|
|
34642
|
+
if (options2.incremental) {
|
|
34643
|
+
deps.logger.log(`- Incremental mode: enabled`);
|
|
34644
|
+
}
|
|
34399
34645
|
const config = await deps.loadConfig(configPath);
|
|
34400
34646
|
const generator = deps.createGenerator({
|
|
34401
34647
|
contentDir,
|
|
@@ -34403,6 +34649,9 @@ async function handleGenerateCommand(options2, deps = defaultDeps2) {
|
|
|
34403
34649
|
templatesDir,
|
|
34404
34650
|
config
|
|
34405
34651
|
});
|
|
34652
|
+
if (options2.incremental) {
|
|
34653
|
+
generator.enableIncrementalMode();
|
|
34654
|
+
}
|
|
34406
34655
|
await generator.initialize();
|
|
34407
34656
|
await generator.generate();
|
|
34408
34657
|
deps.logger.log("Site generation completed successfully!");
|
|
@@ -34412,17 +34661,17 @@ async function handleGenerateCommand(options2, deps = defaultDeps2) {
|
|
|
34412
34661
|
}
|
|
34413
34662
|
}
|
|
34414
34663
|
function registerGenerateCommand(program2) {
|
|
34415
|
-
return program2.command("generate").description("Generate static site from markdown content").option("-c, --config <file>", "Config file path", "bunki.config.ts").option("-d, --content <dir>", "Content directory", DEFAULT_CONTENT_DIR).option("-o, --output <dir>", "Output directory", DEFAULT_OUTPUT_DIR).option("-t, --templates <dir>", "Templates directory", DEFAULT_TEMPLATES_DIR).action(async (options2) => {
|
|
34664
|
+
return program2.command("generate").description("Generate static site from markdown content").option("-c, --config <file>", "Config file path", "bunki.config.ts").option("-d, --content <dir>", "Content directory", DEFAULT_CONTENT_DIR).option("-o, --output <dir>", "Output directory", DEFAULT_OUTPUT_DIR).option("-t, --templates <dir>", "Templates directory", DEFAULT_TEMPLATES_DIR).option("-i, --incremental", "Enable incremental builds (only rebuild changed files)").action(async (options2) => {
|
|
34416
34665
|
await handleGenerateCommand(options2);
|
|
34417
34666
|
});
|
|
34418
34667
|
}
|
|
34419
34668
|
|
|
34420
34669
|
// src/utils/image-uploader.ts
|
|
34421
|
-
import
|
|
34670
|
+
import path12 from "path";
|
|
34422
34671
|
|
|
34423
34672
|
// src/utils/s3-uploader.ts
|
|
34424
34673
|
var {S3Client } = globalThis.Bun;
|
|
34425
|
-
import
|
|
34674
|
+
import path11 from "path";
|
|
34426
34675
|
|
|
34427
34676
|
class S3Uploader {
|
|
34428
34677
|
s3Config;
|
|
@@ -34545,8 +34794,8 @@ class S3Uploader {
|
|
|
34545
34794
|
let failedCount = 0;
|
|
34546
34795
|
const uploadTasks = imageFiles.map((imageFile) => async () => {
|
|
34547
34796
|
try {
|
|
34548
|
-
const imagePath =
|
|
34549
|
-
const filename =
|
|
34797
|
+
const imagePath = path11.join(imagesDir, imageFile);
|
|
34798
|
+
const filename = path11.basename(imagePath);
|
|
34550
34799
|
const file = Bun.file(imagePath);
|
|
34551
34800
|
const contentType = file.type;
|
|
34552
34801
|
if (process.env.BUNKI_DRY_RUN === "true") {} else {
|
|
@@ -34580,10 +34829,10 @@ function createUploader(config) {
|
|
|
34580
34829
|
}
|
|
34581
34830
|
|
|
34582
34831
|
// src/utils/image-uploader.ts
|
|
34583
|
-
var DEFAULT_IMAGES_DIR =
|
|
34832
|
+
var DEFAULT_IMAGES_DIR = path12.join(process.cwd(), "assets");
|
|
34584
34833
|
async function uploadImages(options2 = {}) {
|
|
34585
34834
|
try {
|
|
34586
|
-
const imagesDir =
|
|
34835
|
+
const imagesDir = path12.resolve(options2.images || DEFAULT_IMAGES_DIR);
|
|
34587
34836
|
if (!await fileExists(imagesDir)) {
|
|
34588
34837
|
console.log(`Creating images directory at ${imagesDir}...`);
|
|
34589
34838
|
await ensureDir(imagesDir);
|
|
@@ -34620,7 +34869,7 @@ async function uploadImages(options2 = {}) {
|
|
|
34620
34869
|
const uploader = createUploader(s3Config);
|
|
34621
34870
|
const imageUrlMap = await uploader.uploadImages(imagesDir, options2.minYear);
|
|
34622
34871
|
if (options2.outputJson) {
|
|
34623
|
-
const outputFile =
|
|
34872
|
+
const outputFile = path12.resolve(options2.outputJson);
|
|
34624
34873
|
await Bun.write(outputFile, JSON.stringify(imageUrlMap, null, 2));
|
|
34625
34874
|
console.log(`Image URL mapping saved to ${outputFile}`);
|
|
34626
34875
|
}
|
|
@@ -34666,438 +34915,476 @@ function registerImagesPushCommand(program2) {
|
|
|
34666
34915
|
}
|
|
34667
34916
|
|
|
34668
34917
|
// src/cli/commands/init.ts
|
|
34669
|
-
import
|
|
34918
|
+
import path13 from "path";
|
|
34919
|
+
var defaultDependencies = {
|
|
34920
|
+
createDefaultConfig,
|
|
34921
|
+
ensureDir,
|
|
34922
|
+
writeFile: (filePath, data) => Bun.write(filePath, data),
|
|
34923
|
+
logger: console,
|
|
34924
|
+
exit: (code) => process.exit(code)
|
|
34925
|
+
};
|
|
34926
|
+
async function handleInitCommand(options2, deps = defaultDependencies) {
|
|
34927
|
+
try {
|
|
34928
|
+
const configPath = path13.resolve(options2.config);
|
|
34929
|
+
const configCreated = await deps.createDefaultConfig(configPath);
|
|
34930
|
+
if (!configCreated) {
|
|
34931
|
+
deps.logger.log(`
|
|
34932
|
+
Skipped initialization because the config file already exists`);
|
|
34933
|
+
return;
|
|
34934
|
+
}
|
|
34935
|
+
deps.logger.log("Creating directory structure...");
|
|
34936
|
+
const baseDir = process.cwd();
|
|
34937
|
+
const contentDir = path13.join(baseDir, "content");
|
|
34938
|
+
const templatesDir = path13.join(baseDir, "templates");
|
|
34939
|
+
const stylesDir = path13.join(templatesDir, "styles");
|
|
34940
|
+
const publicDir = path13.join(baseDir, "public");
|
|
34941
|
+
await deps.ensureDir(contentDir);
|
|
34942
|
+
await deps.ensureDir(templatesDir);
|
|
34943
|
+
await deps.ensureDir(stylesDir);
|
|
34944
|
+
await deps.ensureDir(publicDir);
|
|
34945
|
+
for (const [filename, content] of Object.entries(getDefaultTemplates())) {
|
|
34946
|
+
await deps.writeFile(path13.join(templatesDir, filename), content);
|
|
34947
|
+
}
|
|
34948
|
+
await deps.writeFile(path13.join(stylesDir, "main.css"), getDefaultCss());
|
|
34949
|
+
await deps.writeFile(path13.join(contentDir, "welcome.md"), getSamplePost());
|
|
34950
|
+
deps.logger.log(`
|
|
34951
|
+
Initialization complete! Here are the next steps:`);
|
|
34952
|
+
deps.logger.log("1. Edit bunki.config.ts to configure your site");
|
|
34953
|
+
deps.logger.log("2. Add markdown files to the content directory");
|
|
34954
|
+
deps.logger.log('3. Run "bunki generate" to build your site');
|
|
34955
|
+
deps.logger.log('4. Run "bunki serve" to preview your site locally');
|
|
34956
|
+
} catch (error) {
|
|
34957
|
+
deps.logger.error("Error initializing site:", error);
|
|
34958
|
+
deps.exit(1);
|
|
34959
|
+
}
|
|
34960
|
+
}
|
|
34961
|
+
function registerInitCommand(program2, deps = defaultDependencies) {
|
|
34962
|
+
return program2.command("init").description("Initialize a new site with default structure").option("-c, --config <file>", "Path to config file", "bunki.config.ts").action(async (options2) => {
|
|
34963
|
+
await handleInitCommand(options2, deps);
|
|
34964
|
+
});
|
|
34965
|
+
}
|
|
34966
|
+
function getDefaultTemplates() {
|
|
34967
|
+
return {
|
|
34968
|
+
"base.njk": String.raw`<!DOCTYPE html>
|
|
34969
|
+
<html lang="en">
|
|
34970
|
+
<head>
|
|
34971
|
+
<meta charset="UTF-8">
|
|
34972
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
34973
|
+
<title>{% block title %}{{ site.title }}{% endblock %}</title>
|
|
34974
|
+
<meta name="description" content="{% block description %}{{ site.description }}{% endblock %}">
|
|
34975
|
+
<link rel="stylesheet" href="/css/style.css">
|
|
34976
|
+
{% block head %}{% endblock %}
|
|
34977
|
+
</head>
|
|
34978
|
+
<body>
|
|
34979
|
+
<header>
|
|
34980
|
+
<div class="container">
|
|
34981
|
+
<h1><a href="/">{{ site.title }}</a></h1>
|
|
34982
|
+
<nav>
|
|
34983
|
+
<ul>
|
|
34984
|
+
<li><a href="/">Home</a></li>
|
|
34985
|
+
<li><a href="/tags/">Tags</a></li>
|
|
34986
|
+
</ul>
|
|
34987
|
+
</nav>
|
|
34988
|
+
</div>
|
|
34989
|
+
</header>
|
|
34670
34990
|
|
|
34671
|
-
|
|
34672
|
-
|
|
34673
|
-
|
|
34674
|
-
|
|
34675
|
-
|
|
34676
|
-
|
|
34677
|
-
|
|
34678
|
-
|
|
34679
|
-
|
|
34680
|
-
|
|
34681
|
-
</
|
|
34682
|
-
|
|
34683
|
-
|
|
34684
|
-
|
|
34685
|
-
|
|
34686
|
-
|
|
34687
|
-
|
|
34688
|
-
|
|
34689
|
-
|
|
34690
|
-
|
|
34691
|
-
|
|
34692
|
-
|
|
34693
|
-
|
|
34694
|
-
|
|
34695
|
-
|
|
34696
|
-
|
|
34697
|
-
|
|
34698
|
-
|
|
34699
|
-
|
|
34700
|
-
|
|
34701
|
-
|
|
34702
|
-
|
|
34703
|
-
|
|
34704
|
-
</
|
|
34705
|
-
|
|
34706
|
-
|
|
34707
|
-
|
|
34708
|
-
|
|
34709
|
-
|
|
34710
|
-
{%
|
|
34711
|
-
|
|
34712
|
-
|
|
34713
|
-
|
|
34714
|
-
|
|
34715
|
-
|
|
34716
|
-
|
|
34717
|
-
|
|
34718
|
-
<
|
|
34719
|
-
|
|
34720
|
-
|
|
34721
|
-
|
|
34722
|
-
|
|
34723
|
-
<a href="/tags/{{ post.tagSlugs[tag] }}/">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
|
34724
|
-
{% endfor %}
|
|
34725
|
-
</span>
|
|
34726
|
-
{% endif %}
|
|
34727
|
-
</div>
|
|
34728
|
-
<div class="post-excerpt">{{ post.excerpt }}</div>
|
|
34729
|
-
<a href="{{ post.url }}" class="read-more">Read more \u2192</a>
|
|
34730
|
-
</article>
|
|
34731
|
-
{% endfor %}
|
|
34732
|
-
</div>
|
|
34733
|
-
|
|
34734
|
-
{% if pagination.totalPages > 1 %}
|
|
34735
|
-
<nav class="pagination">
|
|
34736
|
-
{% if pagination.hasPrevPage %}
|
|
34737
|
-
<a href="{{ pagination.pagePath }}{% if pagination.prevPage > 1 %}page/{{ pagination.prevPage }}/{% endif %}" class="prev">\u2190 Previous</a>
|
|
34738
|
-
{% endif %}
|
|
34739
|
-
|
|
34740
|
-
{% if pagination.hasNextPage %}
|
|
34741
|
-
<a href="{{ pagination.pagePath }}page/{{ pagination.nextPage }}/" class="next">Next \u2192</a>
|
|
34742
|
-
{% endif %}
|
|
34743
|
-
|
|
34744
|
-
<span class="page-info">Page {{ pagination.currentPage }} of {{ pagination.totalPages }}</span>
|
|
34745
|
-
</nav>
|
|
34991
|
+
<main class="container">
|
|
34992
|
+
{% block content %}{% endblock %}
|
|
34993
|
+
</main>
|
|
34994
|
+
|
|
34995
|
+
<footer>
|
|
34996
|
+
<div class="container">
|
|
34997
|
+
<p>© {{ "now" | date("YYYY") }} {{ site.title }}</p>
|
|
34998
|
+
</div>
|
|
34999
|
+
</footer>
|
|
35000
|
+
</body>
|
|
35001
|
+
</html>`,
|
|
35002
|
+
"index.njk": String.raw`{% extends "base.njk" %}
|
|
35003
|
+
|
|
35004
|
+
{% block content %}
|
|
35005
|
+
<h1>Latest Posts</h1>
|
|
35006
|
+
|
|
35007
|
+
{% if posts.length > 0 %}
|
|
35008
|
+
<div class="posts">
|
|
35009
|
+
{% for post in posts %}
|
|
35010
|
+
<article class="post-card">
|
|
35011
|
+
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
|
35012
|
+
<div class="post-meta">
|
|
35013
|
+
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
35014
|
+
{% if post.tags.length > 0 %}
|
|
35015
|
+
<span class="tags">
|
|
35016
|
+
{% for tag in post.tags %}
|
|
35017
|
+
<a href="/tags/{{ post.tagSlugs[tag] }}/">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
|
35018
|
+
{% endfor %}
|
|
35019
|
+
</span>
|
|
35020
|
+
{% endif %}
|
|
35021
|
+
</div>
|
|
35022
|
+
<div class="post-excerpt">{{ post.excerpt }}</div>
|
|
35023
|
+
<a href="{{ post.url }}" class="read-more">Read more \u2192</a>
|
|
35024
|
+
</article>
|
|
35025
|
+
{% endfor %}
|
|
35026
|
+
</div>
|
|
35027
|
+
|
|
35028
|
+
{% if pagination.totalPages > 1 %}
|
|
35029
|
+
<nav class="pagination">
|
|
35030
|
+
{% if pagination.hasPrevPage %}
|
|
35031
|
+
<a href="{{ pagination.pagePath }}{% if pagination.prevPage > 1 %}page/{{ pagination.prevPage }}/{% endif %}" class="prev">\u2190 Previous</a>
|
|
35032
|
+
{% endif %}
|
|
35033
|
+
|
|
35034
|
+
{% if pagination.hasNextPage %}
|
|
35035
|
+
<a href="{{ pagination.pagePath }}page/{{ pagination.nextPage }}/" class="next">Next \u2192</a>
|
|
35036
|
+
{% endif %}
|
|
35037
|
+
|
|
35038
|
+
<span class="page-info">Page {{ pagination.currentPage }} of {{ pagination.totalPages }}</span>
|
|
35039
|
+
</nav>
|
|
35040
|
+
{% endif %}
|
|
35041
|
+
{% else %}
|
|
35042
|
+
<p>No posts yet!</p>
|
|
34746
35043
|
{% endif %}
|
|
34747
|
-
{%
|
|
34748
|
-
|
|
34749
|
-
|
|
34750
|
-
{% endblock %}
|
|
34751
|
-
|
|
34752
|
-
|
|
34753
|
-
|
|
34754
|
-
|
|
34755
|
-
|
|
34756
|
-
{
|
|
34757
|
-
|
|
34758
|
-
{
|
|
34759
|
-
|
|
34760
|
-
|
|
34761
|
-
|
|
34762
|
-
|
|
34763
|
-
|
|
34764
|
-
|
|
34765
|
-
|
|
34766
|
-
|
|
34767
|
-
|
|
34768
|
-
|
|
34769
|
-
|
|
34770
|
-
{
|
|
35044
|
+
{% endblock %}`,
|
|
35045
|
+
"post.njk": String.raw`{% extends "base.njk" %}
|
|
35046
|
+
|
|
35047
|
+
{% block title %}{{ post.title }} | {{ site.title }}{% endblock %}
|
|
35048
|
+
{% block description %}{{ post.excerpt }}{% endblock %}
|
|
35049
|
+
|
|
35050
|
+
{% block content %}
|
|
35051
|
+
<article class="post">
|
|
35052
|
+
<header class="post-header">
|
|
35053
|
+
<h1>{{ post.title }}</h1>
|
|
35054
|
+
<div class="post-meta">
|
|
35055
|
+
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
35056
|
+
{% if post.tags.length > 0 %}
|
|
35057
|
+
<span class="tags">
|
|
35058
|
+
{% for tag in post.tags %}
|
|
35059
|
+
<a href="/tags/{{ post.tagSlugs[tag] }}/">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
|
35060
|
+
{% endfor %}
|
|
35061
|
+
</span>
|
|
35062
|
+
{% endif %}
|
|
35063
|
+
</div>
|
|
35064
|
+
</header>
|
|
35065
|
+
|
|
35066
|
+
<div class="post-content">
|
|
35067
|
+
{{ post.html | safe }}
|
|
34771
35068
|
</div>
|
|
34772
|
-
</
|
|
35069
|
+
</article>
|
|
35070
|
+
{% endblock %}`,
|
|
35071
|
+
"tag.njk": String.raw`{% extends "base.njk" %}
|
|
35072
|
+
|
|
35073
|
+
{% block title %}{{ tag.name }} | {{ site.title }}{% endblock %}
|
|
35074
|
+
{% block description %}Posts tagged with {{ tag.name }} on {{ site.title }}{% endblock %}
|
|
35075
|
+
|
|
35076
|
+
{% block content %}
|
|
35077
|
+
<h1>Posts tagged "{{ tag.name }}"</h1>
|
|
34773
35078
|
|
|
34774
|
-
|
|
34775
|
-
{{
|
|
34776
|
-
</div>
|
|
34777
|
-
</article>
|
|
34778
|
-
{% endblock %}`;
|
|
34779
|
-
|
|
34780
|
-
// src/cli/commands/templates/tag-njk.ts
|
|
34781
|
-
var tagNjk = String.raw`{% extends "base.njk" %}
|
|
34782
|
-
|
|
34783
|
-
{% block title %}{{ tag.name }} | {{ site.title }}{% endblock %}
|
|
34784
|
-
{% block description %}Posts tagged with {{ tag.name }} on {{ site.title }}{% endblock %}
|
|
34785
|
-
|
|
34786
|
-
{% block content %}
|
|
34787
|
-
<h1>Posts tagged "{{ tag.name }}"</h1>
|
|
34788
|
-
|
|
34789
|
-
{% if tag.description %}
|
|
34790
|
-
<div class="tag-description">{{ tag.description }}</div>
|
|
34791
|
-
{% endif %}
|
|
34792
|
-
|
|
34793
|
-
{% if tag.posts.length > 0 %}
|
|
34794
|
-
<div class="posts">
|
|
34795
|
-
{% for post in tag.posts %}
|
|
34796
|
-
<article class="post-card">
|
|
34797
|
-
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
|
34798
|
-
<div class="post-meta">
|
|
34799
|
-
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
34800
|
-
</div>
|
|
34801
|
-
<div class="post-excerpt">{{ post.excerpt }}</div>
|
|
34802
|
-
<a href="{{ post.url }}" class="read-more">Read more \u2192</a>
|
|
34803
|
-
</article>
|
|
34804
|
-
{% endfor %}
|
|
34805
|
-
</div>
|
|
34806
|
-
|
|
34807
|
-
{% if pagination.totalPages > 1 %}
|
|
34808
|
-
<nav class="pagination">
|
|
34809
|
-
{% if pagination.hasPrevPage %}
|
|
34810
|
-
<a href="{{ pagination.pagePath }}{% if pagination.prevPage > 1 %}page/{{ pagination.prevPage }}/{% endif %}" class="prev">\u2190 Previous</a>
|
|
34811
|
-
{% endif %}
|
|
34812
|
-
|
|
34813
|
-
{% if pagination.hasNextPage %}
|
|
34814
|
-
<a href="{{ pagination.pagePath }}page/{{ pagination.nextPage }}/" class="next">Next \u2192</a>
|
|
34815
|
-
{% endif %}
|
|
34816
|
-
|
|
34817
|
-
<span class="page-info">Page {{ pagination.currentPage }} of {{ pagination.totalPages }}</span>
|
|
34818
|
-
</nav>
|
|
35079
|
+
{% if tag.description %}
|
|
35080
|
+
<div class="tag-description">{{ tag.description }}</div>
|
|
34819
35081
|
{% endif %}
|
|
34820
|
-
|
|
34821
|
-
|
|
34822
|
-
|
|
34823
|
-
{%
|
|
34824
|
-
|
|
34825
|
-
|
|
34826
|
-
|
|
34827
|
-
|
|
34828
|
-
|
|
34829
|
-
|
|
34830
|
-
|
|
34831
|
-
|
|
34832
|
-
|
|
34833
|
-
|
|
34834
|
-
|
|
34835
|
-
|
|
34836
|
-
|
|
34837
|
-
|
|
34838
|
-
|
|
34839
|
-
|
|
34840
|
-
|
|
34841
|
-
|
|
35082
|
+
|
|
35083
|
+
{% if tag.posts.length > 0 %}
|
|
35084
|
+
<div class="posts">
|
|
35085
|
+
{% for post in tag.posts %}
|
|
35086
|
+
<article class="post-card">
|
|
35087
|
+
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
|
35088
|
+
<div class="post-meta">
|
|
35089
|
+
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
35090
|
+
</div>
|
|
35091
|
+
<div class="post-excerpt">{{ post.excerpt }}</div>
|
|
35092
|
+
<a href="{{ post.url }}" class="read-more">Read more \u2192</a>
|
|
35093
|
+
</article>
|
|
35094
|
+
{% endfor %}
|
|
35095
|
+
</div>
|
|
35096
|
+
|
|
35097
|
+
{% if pagination.totalPages > 1 %}
|
|
35098
|
+
<nav class="pagination">
|
|
35099
|
+
{% if pagination.hasPrevPage %}
|
|
35100
|
+
<a href="{{ pagination.pagePath }}{% if pagination.prevPage > 1 %}page/{{ pagination.prevPage }}/{% endif %}" class="prev">\u2190 Previous</a>
|
|
35101
|
+
{% endif %}
|
|
35102
|
+
|
|
35103
|
+
{% if pagination.hasNextPage %}
|
|
35104
|
+
<a href="{{ pagination.pagePath }}page/{{ pagination.nextPage }}/" class="next">Next \u2192</a>
|
|
34842
35105
|
{% endif %}
|
|
34843
|
-
|
|
34844
|
-
|
|
34845
|
-
|
|
34846
|
-
|
|
34847
|
-
|
|
34848
|
-
|
|
34849
|
-
{%
|
|
34850
|
-
|
|
34851
|
-
|
|
34852
|
-
|
|
34853
|
-
|
|
34854
|
-
{% block
|
|
34855
|
-
|
|
34856
|
-
|
|
34857
|
-
|
|
34858
|
-
|
|
34859
|
-
|
|
34860
|
-
|
|
34861
|
-
|
|
34862
|
-
|
|
34863
|
-
|
|
34864
|
-
|
|
34865
|
-
|
|
34866
|
-
|
|
34867
|
-
{% if post.tags.length > 0 %}
|
|
34868
|
-
<span class="tags">
|
|
34869
|
-
{% for tag in post.tags %}
|
|
34870
|
-
<a href="/tags/{{ post.tagSlugs[tag] }}/">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
|
34871
|
-
{% endfor %}
|
|
34872
|
-
</span>
|
|
35106
|
+
|
|
35107
|
+
<span class="page-info">Page {{ pagination.currentPage }} of {{ pagination.totalPages }}</span>
|
|
35108
|
+
</nav>
|
|
35109
|
+
{% endif %}
|
|
35110
|
+
{% else %}
|
|
35111
|
+
<p>No posts with this tag yet!</p>
|
|
35112
|
+
{% endif %}
|
|
35113
|
+
{% endblock %}`,
|
|
35114
|
+
"tags.njk": String.raw`{% extends "base.njk" %}
|
|
35115
|
+
|
|
35116
|
+
{% block title %}Tags | {{ site.title }}{% endblock %}
|
|
35117
|
+
{% block description %}Browse all tags on {{ site.title }}{% endblock %}
|
|
35118
|
+
|
|
35119
|
+
{% block content %}
|
|
35120
|
+
<h1>All Tags</h1>
|
|
35121
|
+
|
|
35122
|
+
{% if tags.length > 0 %}
|
|
35123
|
+
<ul class="tags-list">
|
|
35124
|
+
{% for tag in tags %}
|
|
35125
|
+
<li>
|
|
35126
|
+
<a href="/tags/{{ tag.slug }}/">{{ tag.name }}</a>
|
|
35127
|
+
<span class="count">({{ tag.count }})</span>
|
|
35128
|
+
{% if tag.description %}
|
|
35129
|
+
<p class="description">{{ tag.description }}</p>
|
|
34873
35130
|
{% endif %}
|
|
34874
|
-
</
|
|
34875
|
-
|
|
34876
|
-
|
|
34877
|
-
|
|
34878
|
-
|
|
34879
|
-
</div>
|
|
34880
|
-
|
|
34881
|
-
{% if pagination.totalPages > 1 %}
|
|
34882
|
-
<nav class="pagination">
|
|
34883
|
-
{% if pagination.hasPrevPage %}
|
|
34884
|
-
<a href="/{{ year }}/{% if pagination.prevPage > 1 %}page/{{ pagination.prevPage }}/{% endif %}" class="prev">\u2190 Previous</a>
|
|
34885
|
-
{% endif %}
|
|
34886
|
-
|
|
34887
|
-
{% if pagination.hasNextPage %}
|
|
34888
|
-
<a href="/{{ year }}/page/{{ pagination.nextPage }}/" class="next">Next \u2192</a>
|
|
34889
|
-
{% endif %}
|
|
34890
|
-
|
|
34891
|
-
<span class="page-info">Page {{ pagination.currentPage }} of {{ pagination.totalPages }}</span>
|
|
34892
|
-
</nav>
|
|
35131
|
+
</li>
|
|
35132
|
+
{% endfor %}
|
|
35133
|
+
</ul>
|
|
35134
|
+
{% else %}
|
|
35135
|
+
<p>No tags found!</p>
|
|
34893
35136
|
{% endif %}
|
|
34894
|
-
{%
|
|
34895
|
-
|
|
34896
|
-
|
|
34897
|
-
{% endblock %}
|
|
34898
|
-
|
|
34899
|
-
|
|
34900
|
-
|
|
34901
|
-
|
|
34902
|
-
|
|
34903
|
-
|
|
34904
|
-
|
|
34905
|
-
}
|
|
35137
|
+
{% endblock %}`,
|
|
35138
|
+
"archive.njk": String.raw`{% extends "base.njk" %}
|
|
35139
|
+
|
|
35140
|
+
{% block title %}Archive {{ year }} | {{ site.title }}{% endblock %}
|
|
35141
|
+
{% block description %}Posts from {{ year }} on {{ site.title }}{% endblock %}
|
|
35142
|
+
|
|
35143
|
+
{% block content %}
|
|
35144
|
+
<h1>Posts from {{ year }}</h1>
|
|
35145
|
+
|
|
35146
|
+
{% if posts.length > 0 %}
|
|
35147
|
+
<div class="posts">
|
|
35148
|
+
{% for post in posts %}
|
|
35149
|
+
<article class="post-card">
|
|
35150
|
+
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
|
35151
|
+
<div class="post-meta">
|
|
35152
|
+
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
35153
|
+
{% if post.tags.length > 0 %}
|
|
35154
|
+
<span class="tags">
|
|
35155
|
+
{% for tag in post.tags %}
|
|
35156
|
+
<a href="/tags/{{ post.tagSlugs[tag] }}/">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
|
35157
|
+
{% endfor %}
|
|
35158
|
+
</span>
|
|
35159
|
+
{% endif %}
|
|
35160
|
+
</div>
|
|
35161
|
+
<div class="post-excerpt">{{ post.excerpt }}</div>
|
|
35162
|
+
<a href="{{ post.url }}" class="read-more">Read more \u2192</a>
|
|
35163
|
+
</article>
|
|
35164
|
+
{% endfor %}
|
|
35165
|
+
</div>
|
|
34906
35166
|
|
|
34907
|
-
|
|
34908
|
-
|
|
34909
|
-
|
|
34910
|
-
|
|
34911
|
-
|
|
34912
|
-
padding-bottom: 2rem;
|
|
34913
|
-
}
|
|
35167
|
+
{% if pagination.totalPages > 1 %}
|
|
35168
|
+
<nav class="pagination">
|
|
35169
|
+
{% if pagination.hasPrevPage %}
|
|
35170
|
+
<a href="/{{ year }}/{% if pagination.prevPage > 1 %}page/{{ pagination.prevPage }}/{% endif %}" class="prev">\u2190 Previous</a>
|
|
35171
|
+
{% endif %}
|
|
34914
35172
|
|
|
34915
|
-
|
|
34916
|
-
|
|
34917
|
-
|
|
34918
|
-
}
|
|
35173
|
+
{% if pagination.hasNextPage %}
|
|
35174
|
+
<a href="/{{ year }}/page/{{ pagination.nextPage }}/" class="next">Next \u2192</a>
|
|
35175
|
+
{% endif %}
|
|
34919
35176
|
|
|
34920
|
-
|
|
34921
|
-
|
|
35177
|
+
<span class="page-info">Page {{ pagination.currentPage }} of {{ pagination.totalPages }}</span>
|
|
35178
|
+
</nav>
|
|
35179
|
+
{% endif %}
|
|
35180
|
+
{% else %}
|
|
35181
|
+
<p>No posts from {{ year }}!</p>
|
|
35182
|
+
{% endif %}
|
|
35183
|
+
{% endblock %}`
|
|
35184
|
+
};
|
|
34922
35185
|
}
|
|
35186
|
+
function getDefaultCss() {
|
|
35187
|
+
return String.raw`/* Reset & base styles */
|
|
35188
|
+
* {
|
|
35189
|
+
margin: 0;
|
|
35190
|
+
padding: 0;
|
|
35191
|
+
box-sizing: border-box;
|
|
35192
|
+
}
|
|
34923
35193
|
|
|
34924
|
-
|
|
34925
|
-
|
|
34926
|
-
|
|
34927
|
-
|
|
34928
|
-
|
|
35194
|
+
body {
|
|
35195
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
|
35196
|
+
line-height: 1.6;
|
|
35197
|
+
color: #333;
|
|
35198
|
+
background-color: #f8f9fa;
|
|
35199
|
+
padding-bottom: 2rem;
|
|
35200
|
+
}
|
|
34929
35201
|
|
|
34930
|
-
|
|
34931
|
-
|
|
34932
|
-
|
|
34933
|
-
|
|
34934
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
34935
|
-
margin-bottom: 2rem;
|
|
34936
|
-
}
|
|
35202
|
+
a {
|
|
35203
|
+
color: #0066cc;
|
|
35204
|
+
text-decoration: none;
|
|
35205
|
+
}
|
|
34937
35206
|
|
|
34938
|
-
|
|
34939
|
-
|
|
34940
|
-
|
|
34941
|
-
}
|
|
35207
|
+
a:hover {
|
|
35208
|
+
text-decoration: underline;
|
|
35209
|
+
}
|
|
34942
35210
|
|
|
34943
|
-
|
|
34944
|
-
|
|
34945
|
-
|
|
34946
|
-
|
|
35211
|
+
.container {
|
|
35212
|
+
max-width: 800px;
|
|
35213
|
+
margin: 0 auto;
|
|
35214
|
+
padding: 0 1.5rem;
|
|
35215
|
+
}
|
|
34947
35216
|
|
|
34948
|
-
|
|
34949
|
-
|
|
34950
|
-
|
|
35217
|
+
/* Header */
|
|
35218
|
+
header {
|
|
35219
|
+
background-color: #fff;
|
|
35220
|
+
padding: 1.5rem 0;
|
|
35221
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
35222
|
+
margin-bottom: 2rem;
|
|
35223
|
+
}
|
|
34951
35224
|
|
|
34952
|
-
header
|
|
34953
|
-
|
|
34954
|
-
|
|
34955
|
-
|
|
34956
|
-
}
|
|
35225
|
+
header h1 {
|
|
35226
|
+
font-size: 1.8rem;
|
|
35227
|
+
margin: 0;
|
|
35228
|
+
}
|
|
34957
35229
|
|
|
34958
|
-
|
|
34959
|
-
|
|
34960
|
-
|
|
34961
|
-
|
|
34962
|
-
border-radius: 5px;
|
|
34963
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
34964
|
-
}
|
|
35230
|
+
header h1 a {
|
|
35231
|
+
color: #333;
|
|
35232
|
+
text-decoration: none;
|
|
35233
|
+
}
|
|
34965
35234
|
|
|
34966
|
-
|
|
34967
|
-
.
|
|
34968
|
-
|
|
34969
|
-
flex-direction: column;
|
|
34970
|
-
gap: 2rem;
|
|
34971
|
-
}
|
|
35235
|
+
header nav {
|
|
35236
|
+
margin-top: 0.5rem;
|
|
35237
|
+
}
|
|
34972
35238
|
|
|
34973
|
-
|
|
34974
|
-
|
|
34975
|
-
|
|
34976
|
-
|
|
35239
|
+
header nav ul {
|
|
35240
|
+
display: flex;
|
|
35241
|
+
list-style: none;
|
|
35242
|
+
gap: 1.5rem;
|
|
35243
|
+
}
|
|
34977
35244
|
|
|
34978
|
-
|
|
34979
|
-
|
|
34980
|
-
|
|
35245
|
+
/* Main content */
|
|
35246
|
+
main {
|
|
35247
|
+
background-color: #fff;
|
|
35248
|
+
padding: 2rem;
|
|
35249
|
+
border-radius: 5px;
|
|
35250
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
35251
|
+
}
|
|
34981
35252
|
|
|
34982
|
-
|
|
34983
|
-
|
|
34984
|
-
|
|
35253
|
+
/* Posts */
|
|
35254
|
+
.posts {
|
|
35255
|
+
display: flex;
|
|
35256
|
+
flex-direction: column;
|
|
35257
|
+
gap: 2rem;
|
|
35258
|
+
}
|
|
34985
35259
|
|
|
34986
|
-
.post-
|
|
34987
|
-
|
|
34988
|
-
|
|
34989
|
-
|
|
34990
|
-
}
|
|
35260
|
+
.post-card {
|
|
35261
|
+
border-bottom: 1px solid #eee;
|
|
35262
|
+
padding-bottom: 1.5rem;
|
|
35263
|
+
}
|
|
34991
35264
|
|
|
34992
|
-
.post-
|
|
34993
|
-
|
|
34994
|
-
}
|
|
35265
|
+
.post-card:last-child {
|
|
35266
|
+
border-bottom: none;
|
|
35267
|
+
}
|
|
34995
35268
|
|
|
34996
|
-
.
|
|
34997
|
-
|
|
34998
|
-
}
|
|
35269
|
+
.post-card h2 {
|
|
35270
|
+
margin-bottom: 0.5rem;
|
|
35271
|
+
}
|
|
34999
35272
|
|
|
35000
|
-
|
|
35001
|
-
|
|
35002
|
-
|
|
35003
|
-
|
|
35273
|
+
.post-meta {
|
|
35274
|
+
font-size: 0.9rem;
|
|
35275
|
+
color: #6c757d;
|
|
35276
|
+
margin-bottom: 1rem;
|
|
35277
|
+
}
|
|
35004
35278
|
|
|
35005
|
-
.post-
|
|
35006
|
-
|
|
35007
|
-
}
|
|
35279
|
+
.post-excerpt {
|
|
35280
|
+
margin-bottom: 1rem;
|
|
35281
|
+
}
|
|
35008
35282
|
|
|
35009
|
-
.
|
|
35010
|
-
|
|
35011
|
-
|
|
35012
|
-
.post-content blockquote {
|
|
35013
|
-
margin-bottom: 1.5rem;
|
|
35014
|
-
}
|
|
35283
|
+
.read-more {
|
|
35284
|
+
font-weight: 500;
|
|
35285
|
+
}
|
|
35015
35286
|
|
|
35016
|
-
|
|
35017
|
-
.post-
|
|
35018
|
-
|
|
35019
|
-
|
|
35020
|
-
margin-bottom: 1rem;
|
|
35021
|
-
}
|
|
35287
|
+
/* Single post */
|
|
35288
|
+
.post-header {
|
|
35289
|
+
margin-bottom: 2rem;
|
|
35290
|
+
}
|
|
35022
35291
|
|
|
35023
|
-
.post-content
|
|
35024
|
-
|
|
35025
|
-
|
|
35026
|
-
display: block;
|
|
35027
|
-
margin: 2rem auto;
|
|
35028
|
-
}
|
|
35292
|
+
.post-content {
|
|
35293
|
+
line-height: 1.8;
|
|
35294
|
+
}
|
|
35029
35295
|
|
|
35030
|
-
.post-content
|
|
35031
|
-
|
|
35032
|
-
|
|
35033
|
-
|
|
35034
|
-
|
|
35035
|
-
|
|
35036
|
-
}
|
|
35296
|
+
.post-content p,
|
|
35297
|
+
.post-content ul,
|
|
35298
|
+
.post-content ol,
|
|
35299
|
+
.post-content blockquote {
|
|
35300
|
+
margin-bottom: 1.5rem;
|
|
35301
|
+
}
|
|
35037
35302
|
|
|
35038
|
-
.post-content
|
|
35039
|
-
|
|
35040
|
-
|
|
35041
|
-
|
|
35042
|
-
|
|
35043
|
-
|
|
35044
|
-
}
|
|
35303
|
+
.post-content h2,
|
|
35304
|
+
.post-content h3,
|
|
35305
|
+
.post-content h4 {
|
|
35306
|
+
margin-top: 2rem;
|
|
35307
|
+
margin-bottom: 1rem;
|
|
35308
|
+
}
|
|
35045
35309
|
|
|
35046
|
-
.post-content
|
|
35047
|
-
|
|
35048
|
-
|
|
35049
|
-
|
|
35310
|
+
.post-content img {
|
|
35311
|
+
max-width: 100%;
|
|
35312
|
+
height: auto;
|
|
35313
|
+
display: block;
|
|
35314
|
+
margin: 2rem auto;
|
|
35315
|
+
}
|
|
35050
35316
|
|
|
35051
|
-
|
|
35052
|
-
|
|
35053
|
-
|
|
35054
|
-
|
|
35055
|
-
|
|
35317
|
+
.post-content pre {
|
|
35318
|
+
background-color: #f5f5f5;
|
|
35319
|
+
padding: 1rem;
|
|
35320
|
+
border-radius: 4px;
|
|
35321
|
+
overflow-x: auto;
|
|
35322
|
+
margin-bottom: 1.5rem;
|
|
35323
|
+
}
|
|
35056
35324
|
|
|
35057
|
-
.
|
|
35058
|
-
|
|
35059
|
-
|
|
35325
|
+
.post-content code {
|
|
35326
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
35327
|
+
font-size: 0.9em;
|
|
35328
|
+
background-color: #f5f5f5;
|
|
35329
|
+
padding: 0.2em 0.4em;
|
|
35330
|
+
border-radius: 3px;
|
|
35331
|
+
}
|
|
35060
35332
|
|
|
35061
|
-
.
|
|
35062
|
-
|
|
35063
|
-
|
|
35333
|
+
.post-content pre code {
|
|
35334
|
+
padding: 0;
|
|
35335
|
+
background-color: transparent;
|
|
35336
|
+
}
|
|
35064
35337
|
|
|
35065
|
-
|
|
35066
|
-
|
|
35067
|
-
|
|
35068
|
-
|
|
35338
|
+
/* Tags */
|
|
35339
|
+
.tags a {
|
|
35340
|
+
display: inline-block;
|
|
35341
|
+
margin-left: 0.5rem;
|
|
35342
|
+
}
|
|
35069
35343
|
|
|
35070
|
-
.tags-list
|
|
35071
|
-
|
|
35072
|
-
|
|
35073
|
-
color: #6c757d;
|
|
35074
|
-
}
|
|
35344
|
+
.tags-list {
|
|
35345
|
+
list-style: none;
|
|
35346
|
+
}
|
|
35075
35347
|
|
|
35076
|
-
|
|
35077
|
-
|
|
35078
|
-
|
|
35079
|
-
justify-content: space-between;
|
|
35080
|
-
align-items: center;
|
|
35081
|
-
margin-top: 2rem;
|
|
35082
|
-
padding-top: 1rem;
|
|
35083
|
-
border-top: 1px solid #eee;
|
|
35084
|
-
}
|
|
35348
|
+
.tags-list li {
|
|
35349
|
+
margin-bottom: 1rem;
|
|
35350
|
+
}
|
|
35085
35351
|
|
|
35086
|
-
.
|
|
35087
|
-
|
|
35088
|
-
|
|
35089
|
-
}
|
|
35352
|
+
.tags-list .count {
|
|
35353
|
+
color: #6c757d;
|
|
35354
|
+
font-size: 0.9rem;
|
|
35355
|
+
}
|
|
35090
35356
|
|
|
35091
|
-
|
|
35092
|
-
|
|
35093
|
-
|
|
35094
|
-
|
|
35095
|
-
|
|
35096
|
-
|
|
35097
|
-
|
|
35357
|
+
.tags-list .description {
|
|
35358
|
+
margin-top: 0.25rem;
|
|
35359
|
+
font-size: 0.9rem;
|
|
35360
|
+
color: #6c757d;
|
|
35361
|
+
}
|
|
35362
|
+
|
|
35363
|
+
/* Pagination */
|
|
35364
|
+
.pagination {
|
|
35365
|
+
display: flex;
|
|
35366
|
+
justify-content: space-between;
|
|
35367
|
+
align-items: center;
|
|
35368
|
+
margin-top: 2rem;
|
|
35369
|
+
padding-top: 1rem;
|
|
35370
|
+
border-top: 1px solid #eee;
|
|
35371
|
+
}
|
|
35098
35372
|
|
|
35099
|
-
|
|
35100
|
-
|
|
35373
|
+
.pagination .page-info {
|
|
35374
|
+
color: #6c757d;
|
|
35375
|
+
font-size: 0.9rem;
|
|
35376
|
+
}
|
|
35377
|
+
|
|
35378
|
+
/* Footer */
|
|
35379
|
+
footer {
|
|
35380
|
+
text-align: center;
|
|
35381
|
+
padding: 2rem 0;
|
|
35382
|
+
color: #6c757d;
|
|
35383
|
+
font-size: 0.9rem;
|
|
35384
|
+
}`;
|
|
35385
|
+
}
|
|
35386
|
+
function getSamplePost() {
|
|
35387
|
+
return `---
|
|
35101
35388
|
title: Welcome to Bunki
|
|
35102
35389
|
date: 2025-01-15T12:00:00Z
|
|
35103
35390
|
tags: [getting-started, bunki]
|
|
@@ -35148,68 +35435,10 @@ function hello() {
|
|
|
35148
35435
|
4. Run \`bunki generate\` to build your site
|
|
35149
35436
|
5. Run \`bunki serve\` to preview your site locally
|
|
35150
35437
|
`;
|
|
35151
|
-
|
|
35152
|
-
// src/cli/commands/templates/index.ts
|
|
35153
|
-
var nunjucks3 = {
|
|
35154
|
-
"base.njk": baseNjk,
|
|
35155
|
-
"index.njk": indexNjk,
|
|
35156
|
-
"post.njk": postNjk,
|
|
35157
|
-
"tag.njk": tagNjk,
|
|
35158
|
-
"tags.njk": tagsNjk,
|
|
35159
|
-
"archive.njk": archiveNjk
|
|
35160
|
-
};
|
|
35161
|
-
|
|
35162
|
-
// src/cli/commands/init.ts
|
|
35163
|
-
var defaultDependencies = {
|
|
35164
|
-
createDefaultConfig,
|
|
35165
|
-
ensureDir,
|
|
35166
|
-
writeFile: (filePath, data) => Bun.write(filePath, data),
|
|
35167
|
-
logger: console,
|
|
35168
|
-
exit: (code) => process.exit(code)
|
|
35169
|
-
};
|
|
35170
|
-
async function handleInitCommand(options2, deps = defaultDependencies) {
|
|
35171
|
-
try {
|
|
35172
|
-
const configPath = path12.resolve(options2.config);
|
|
35173
|
-
const configCreated = await deps.createDefaultConfig(configPath);
|
|
35174
|
-
if (!configCreated) {
|
|
35175
|
-
deps.logger.log(`
|
|
35176
|
-
Skipped initialization because the config file already exists`);
|
|
35177
|
-
return;
|
|
35178
|
-
}
|
|
35179
|
-
deps.logger.log("Creating directory structure...");
|
|
35180
|
-
const baseDir = process.cwd();
|
|
35181
|
-
const contentDir = path12.join(baseDir, "content");
|
|
35182
|
-
const templatesDir = path12.join(baseDir, "templates");
|
|
35183
|
-
const stylesDir = path12.join(templatesDir, "styles");
|
|
35184
|
-
const publicDir = path12.join(baseDir, "public");
|
|
35185
|
-
await deps.ensureDir(contentDir);
|
|
35186
|
-
await deps.ensureDir(templatesDir);
|
|
35187
|
-
await deps.ensureDir(stylesDir);
|
|
35188
|
-
await deps.ensureDir(publicDir);
|
|
35189
|
-
for (const [filename, content] of Object.entries(nunjucks3)) {
|
|
35190
|
-
await deps.writeFile(path12.join(templatesDir, filename), content);
|
|
35191
|
-
}
|
|
35192
|
-
await deps.writeFile(path12.join(stylesDir, "main.css"), defaultCss);
|
|
35193
|
-
await deps.writeFile(path12.join(contentDir, "welcome.md"), samplePost);
|
|
35194
|
-
deps.logger.log(`
|
|
35195
|
-
Initialization complete! Here are the next steps:`);
|
|
35196
|
-
deps.logger.log("1. Edit bunki.config.ts to configure your site");
|
|
35197
|
-
deps.logger.log("2. Add markdown files to the content directory");
|
|
35198
|
-
deps.logger.log('3. Run "bunki generate" to build your site');
|
|
35199
|
-
deps.logger.log('4. Run "bunki serve" to preview your site locally');
|
|
35200
|
-
} catch (error) {
|
|
35201
|
-
deps.logger.error("Error initializing site:", error);
|
|
35202
|
-
deps.exit(1);
|
|
35203
|
-
}
|
|
35204
|
-
}
|
|
35205
|
-
function registerInitCommand(program2, deps = defaultDependencies) {
|
|
35206
|
-
return program2.command("init").description("Initialize a new site with default structure").option("-c, --config <file>", "Path to config file", "bunki.config.ts").action(async (options2) => {
|
|
35207
|
-
await handleInitCommand(options2, deps);
|
|
35208
|
-
});
|
|
35209
35438
|
}
|
|
35210
35439
|
|
|
35211
35440
|
// src/cli/commands/new-post.ts
|
|
35212
|
-
import
|
|
35441
|
+
import path14 from "path";
|
|
35213
35442
|
var defaultDeps4 = {
|
|
35214
35443
|
writeFile: (filePath, data) => Bun.write(filePath, data),
|
|
35215
35444
|
now: () => new Date,
|
|
@@ -35233,7 +35462,7 @@ async function handleNewCommand(title, options2, deps = defaultDeps4) {
|
|
|
35233
35462
|
` + `# ${title}
|
|
35234
35463
|
|
|
35235
35464
|
`;
|
|
35236
|
-
const filePath =
|
|
35465
|
+
const filePath = path14.join(DEFAULT_CONTENT_DIR, `${slug}.md`);
|
|
35237
35466
|
await deps.writeFile(filePath, frontmatter);
|
|
35238
35467
|
deps.logger.log(`Created new post: ${filePath}`);
|
|
35239
35468
|
return filePath;
|
|
@@ -35250,11 +35479,11 @@ function registerNewCommand(program2) {
|
|
|
35250
35479
|
}
|
|
35251
35480
|
|
|
35252
35481
|
// src/cli/commands/serve.ts
|
|
35253
|
-
import
|
|
35482
|
+
import path16 from "path";
|
|
35254
35483
|
|
|
35255
35484
|
// src/server.ts
|
|
35256
35485
|
import fs2 from "fs";
|
|
35257
|
-
import
|
|
35486
|
+
import path15 from "path";
|
|
35258
35487
|
async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
35259
35488
|
try {
|
|
35260
35489
|
const stats = await fs2.promises.stat(outputDir);
|
|
@@ -35289,18 +35518,18 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
|
35289
35518
|
let filePath = "";
|
|
35290
35519
|
if (homePaginationMatch) {
|
|
35291
35520
|
const pageNumber = homePaginationMatch[1];
|
|
35292
|
-
filePath =
|
|
35521
|
+
filePath = path15.join(outputDir, "page", pageNumber, "index.html");
|
|
35293
35522
|
} else if (tagPaginationMatch) {
|
|
35294
35523
|
const tagSlug = tagPaginationMatch[1];
|
|
35295
35524
|
const pageNumber = tagPaginationMatch[2];
|
|
35296
|
-
filePath =
|
|
35525
|
+
filePath = path15.join(outputDir, "tags", tagSlug, "page", pageNumber, "index.html");
|
|
35297
35526
|
} else if (yearPaginationMatch) {
|
|
35298
35527
|
const year = yearPaginationMatch[1];
|
|
35299
35528
|
const pageNumber = yearPaginationMatch[2];
|
|
35300
|
-
filePath =
|
|
35529
|
+
filePath = path15.join(outputDir, year, "page", pageNumber, "index.html");
|
|
35301
35530
|
} else {
|
|
35302
|
-
const directPath =
|
|
35303
|
-
const withoutSlash =
|
|
35531
|
+
const directPath = path15.join(outputDir, pathname);
|
|
35532
|
+
const withoutSlash = path15.join(outputDir, pathname + ".html");
|
|
35304
35533
|
const withHtml = pathname.endsWith(".html") ? directPath : withoutSlash;
|
|
35305
35534
|
const bunFileDirect = Bun.file(directPath);
|
|
35306
35535
|
const bunFileHtml = Bun.file(withHtml);
|
|
@@ -35309,7 +35538,7 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
|
35309
35538
|
} else if (await bunFileHtml.exists()) {
|
|
35310
35539
|
filePath = withHtml;
|
|
35311
35540
|
} else {
|
|
35312
|
-
const indexPath =
|
|
35541
|
+
const indexPath = path15.join(outputDir, pathname, "index.html");
|
|
35313
35542
|
const bunFileIndex = Bun.file(indexPath);
|
|
35314
35543
|
if (await bunFileIndex.exists()) {
|
|
35315
35544
|
filePath = indexPath;
|
|
@@ -35323,7 +35552,7 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
|
35323
35552
|
}
|
|
35324
35553
|
}
|
|
35325
35554
|
console.log(`Serving file: ${filePath}`);
|
|
35326
|
-
const extname =
|
|
35555
|
+
const extname = path15.extname(filePath);
|
|
35327
35556
|
let contentType = "text/html";
|
|
35328
35557
|
switch (extname) {
|
|
35329
35558
|
case ".js":
|
|
@@ -35382,7 +35611,7 @@ var defaultDeps5 = {
|
|
|
35382
35611
|
};
|
|
35383
35612
|
async function handleServeCommand(options2, deps = defaultDeps5) {
|
|
35384
35613
|
try {
|
|
35385
|
-
const outputDir =
|
|
35614
|
+
const outputDir = path16.resolve(options2.output);
|
|
35386
35615
|
const port = parseInt(options2.port, 10);
|
|
35387
35616
|
await deps.startServer(outputDir, port);
|
|
35388
35617
|
} catch (error) {
|