bunki 0.18.5 → 0.19.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 +83 -1
- package/dist/cli.js +428 -283
- package/dist/fragments/json-ld.njk +59 -0
- package/dist/fragments/og-image.njk +21 -0
- package/dist/fragments/pagination.njk +12 -0
- package/dist/fragments/share-buttons.njk +21 -0
- package/dist/index.js +0 -32886
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -5,15 +5,29 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
8
13
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
9
21
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
22
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
23
|
for (let key of __getOwnPropNames(mod))
|
|
12
24
|
if (!__hasOwnProp.call(to, key))
|
|
13
25
|
__defProp(to, key, {
|
|
14
|
-
get: (
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
15
27
|
enumerable: true
|
|
16
28
|
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
17
31
|
return to;
|
|
18
32
|
};
|
|
19
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
@@ -28555,11 +28569,11 @@ function registerCssCommand(program2) {
|
|
|
28555
28569
|
}
|
|
28556
28570
|
|
|
28557
28571
|
// src/cli/commands/generate.ts
|
|
28558
|
-
import
|
|
28572
|
+
import path11 from "path";
|
|
28559
28573
|
|
|
28560
28574
|
// src/site-generator.ts
|
|
28561
28575
|
var import_slugify = __toESM(require_slugify(), 1);
|
|
28562
|
-
import
|
|
28576
|
+
import path10 from "path";
|
|
28563
28577
|
|
|
28564
28578
|
// src/parser.ts
|
|
28565
28579
|
import path5 from "path";
|
|
@@ -33136,6 +33150,12 @@ function createMarked(cdnConfig) {
|
|
|
33136
33150
|
return markdown2;
|
|
33137
33151
|
},
|
|
33138
33152
|
postprocess(html) {
|
|
33153
|
+
if (cdnConfig?.enabled && cdnConfig.postYear) {
|
|
33154
|
+
const year = cdnConfig.postYear;
|
|
33155
|
+
const base = cdnConfig.baseUrl;
|
|
33156
|
+
html = html.replace(/src=(["'])\.\/\_assets\/([^\s"']+)\1/g, (_m, q2, filename) => `src=${q2}${base}/${year}/${filename}${q2}`);
|
|
33157
|
+
html = html.replace(/!\[([^\]]*)\]\(\.\/\_assets\/([^\s)]+)\)/g, (_m, alt, filename) => `<img src="${base}/${year}/${filename}" alt="${alt}" loading="lazy">`);
|
|
33158
|
+
}
|
|
33139
33159
|
html = html.replace(YOUTUBE_EMBED_REGEX, '<div class="video-container"><iframe src="https://www.youtube.com/embed/$4" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen loading="lazy"></iframe></div>');
|
|
33140
33160
|
html = html.replace(/<img /g, '<img loading="lazy" ');
|
|
33141
33161
|
return html.replace(EXTERNAL_LINK_REGEX, (match, protocol, rest) => {
|
|
@@ -33374,7 +33394,9 @@ async function parseMarkdownFile(filePath, cdnConfig) {
|
|
|
33374
33394
|
const slug = getBaseFilename(filePath);
|
|
33375
33395
|
const pacificDate = toPacificTime(data.date);
|
|
33376
33396
|
const postYear = getPacificYear(data.date);
|
|
33377
|
-
const
|
|
33397
|
+
const yearFromPath = filePath.match(/\/(\d{4})\//)?.[1];
|
|
33398
|
+
const resolvedYear = String(postYear) !== "NaN" ? String(postYear) : yearFromPath;
|
|
33399
|
+
const cdnConfigWithYear = cdnConfig && resolvedYear ? { ...cdnConfig, postYear: resolvedYear } : undefined;
|
|
33378
33400
|
const sanitizedHtml = convertMarkdownToHtml(content, cdnConfigWithYear);
|
|
33379
33401
|
const post = {
|
|
33380
33402
|
title: data.title,
|
|
@@ -33964,31 +33986,6 @@ ${categoryTags}`;
|
|
|
33964
33986
|
return itemXml;
|
|
33965
33987
|
}
|
|
33966
33988
|
|
|
33967
|
-
// src/utils/pagination.ts
|
|
33968
|
-
function createPagination(items, currentPage, pageSize, pagePath) {
|
|
33969
|
-
const totalItems = items.length;
|
|
33970
|
-
const totalPages = Math.ceil(totalItems / pageSize);
|
|
33971
|
-
return {
|
|
33972
|
-
currentPage,
|
|
33973
|
-
totalPages,
|
|
33974
|
-
hasNextPage: currentPage < totalPages,
|
|
33975
|
-
hasPrevPage: currentPage > 1,
|
|
33976
|
-
nextPage: currentPage < totalPages ? currentPage + 1 : null,
|
|
33977
|
-
prevPage: currentPage > 1 ? currentPage - 1 : null,
|
|
33978
|
-
pageSize,
|
|
33979
|
-
totalItems,
|
|
33980
|
-
pagePath
|
|
33981
|
-
};
|
|
33982
|
-
}
|
|
33983
|
-
function getPaginatedItems(items, page, pageSize) {
|
|
33984
|
-
const startIndex = (page - 1) * pageSize;
|
|
33985
|
-
const endIndex = startIndex + pageSize;
|
|
33986
|
-
return items.slice(startIndex, endIndex);
|
|
33987
|
-
}
|
|
33988
|
-
function getTotalPages(totalItems, pageSize) {
|
|
33989
|
-
return Math.ceil(totalItems / pageSize);
|
|
33990
|
-
}
|
|
33991
|
-
|
|
33992
33989
|
// src/generators/feeds.ts
|
|
33993
33990
|
function makeAbsoluteUrl(imageUrl, baseUrl) {
|
|
33994
33991
|
return imageUrl.startsWith("http") ? imageUrl : `${baseUrl}${imageUrl}`;
|
|
@@ -34058,10 +34055,6 @@ function generateSitemap(site, config, pageSize = 10) {
|
|
|
34058
34055
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
34059
34056
|
`;
|
|
34060
34057
|
sitemapContent += buildSitemapUrl(`${config.baseUrl}/`, currentDate, "daily", 1);
|
|
34061
|
-
const totalHomePages = getTotalPages(site.posts.length, pageSize);
|
|
34062
|
-
for (let page = 2;page <= totalHomePages; page++) {
|
|
34063
|
-
sitemapContent += buildSitemapUrl(`${config.baseUrl}/page/${page}/`, currentDate, "daily", 0.8);
|
|
34064
|
-
}
|
|
34065
34058
|
for (const post of site.posts) {
|
|
34066
34059
|
const postUrl = `${config.baseUrl}${post.url}`;
|
|
34067
34060
|
const postDate = new Date(post.date).toISOString();
|
|
@@ -34077,20 +34070,12 @@ function generateSitemap(site, config, pageSize = 10) {
|
|
|
34077
34070
|
const mostRecentPost = tagData.posts[0];
|
|
34078
34071
|
const tagPriority = mostRecentPost ? calculateFreshnessPriority(mostRecentPost.date, 0.4, now) : 0.4;
|
|
34079
34072
|
sitemapContent += buildSitemapUrl(tagUrl, currentDate, "weekly", tagPriority);
|
|
34080
|
-
const totalTagPages = getTotalPages(tagData.posts.length, pageSize);
|
|
34081
|
-
for (let page = 2;page <= totalTagPages; page++) {
|
|
34082
|
-
sitemapContent += buildSitemapUrl(`${config.baseUrl}/tags/${tagData.slug}/page/${page}/`, currentDate, "weekly", Math.max(0.3, tagPriority - 0.1));
|
|
34083
|
-
}
|
|
34084
34073
|
}
|
|
34085
34074
|
for (const [year, yearPosts] of Object.entries(site.postsByYear)) {
|
|
34086
34075
|
const currentYear = new Date().getFullYear();
|
|
34087
34076
|
const isCurrentYear = parseInt(year) === currentYear;
|
|
34088
34077
|
const yearPriority = isCurrentYear ? 0.7 : 0.5;
|
|
34089
34078
|
sitemapContent += buildSitemapUrl(`${config.baseUrl}/${year}/`, currentDate, isCurrentYear ? "weekly" : "monthly", yearPriority);
|
|
34090
|
-
const totalYearPages = getTotalPages(yearPosts.length, pageSize);
|
|
34091
|
-
for (let page = 2;page <= totalYearPages; page++) {
|
|
34092
|
-
sitemapContent += buildSitemapUrl(`${config.baseUrl}/${year}/page/${page}/`, currentDate, isCurrentYear ? "weekly" : "monthly", yearPriority - 0.1);
|
|
34093
|
-
}
|
|
34094
34079
|
}
|
|
34095
34080
|
sitemapContent += `</urlset>`;
|
|
34096
34081
|
return sitemapContent;
|
|
@@ -34129,6 +34114,31 @@ Sitemap: ${config.baseUrl}/sitemap.xml
|
|
|
34129
34114
|
var import_nunjucks = __toESM(require_nunjucks(), 1);
|
|
34130
34115
|
import path7 from "path";
|
|
34131
34116
|
|
|
34117
|
+
// src/utils/pagination.ts
|
|
34118
|
+
function createPagination(items, currentPage, pageSize, pagePath) {
|
|
34119
|
+
const totalItems = items.length;
|
|
34120
|
+
const totalPages = Math.ceil(totalItems / pageSize);
|
|
34121
|
+
return {
|
|
34122
|
+
currentPage,
|
|
34123
|
+
totalPages,
|
|
34124
|
+
hasNextPage: currentPage < totalPages,
|
|
34125
|
+
hasPrevPage: currentPage > 1,
|
|
34126
|
+
nextPage: currentPage < totalPages ? currentPage + 1 : null,
|
|
34127
|
+
prevPage: currentPage > 1 ? currentPage - 1 : null,
|
|
34128
|
+
pageSize,
|
|
34129
|
+
totalItems,
|
|
34130
|
+
pagePath
|
|
34131
|
+
};
|
|
34132
|
+
}
|
|
34133
|
+
function getPaginatedItems(items, page, pageSize) {
|
|
34134
|
+
const startIndex = (page - 1) * pageSize;
|
|
34135
|
+
const endIndex = startIndex + pageSize;
|
|
34136
|
+
return items.slice(startIndex, endIndex);
|
|
34137
|
+
}
|
|
34138
|
+
function getTotalPages(totalItems, pageSize) {
|
|
34139
|
+
return Math.ceil(totalItems / pageSize);
|
|
34140
|
+
}
|
|
34141
|
+
|
|
34132
34142
|
// src/utils/schema-factory.ts
|
|
34133
34143
|
function generateCollectionSchemas(config, options2) {
|
|
34134
34144
|
const schemas = [
|
|
@@ -34444,8 +34454,13 @@ function displayMetrics(metrics) {
|
|
|
34444
34454
|
|
|
34445
34455
|
// src/utils/template-engine.ts
|
|
34446
34456
|
var import_nunjucks2 = __toESM(require_nunjucks(), 1);
|
|
34457
|
+
import path9 from "path";
|
|
34458
|
+
import { existsSync } from "fs";
|
|
34459
|
+
var _distFragments = path9.join(import.meta.dir, "fragments");
|
|
34460
|
+
var _srcFragments = path9.join(import.meta.dir, "../fragments");
|
|
34461
|
+
var BUNKI_FRAGMENTS_DIR = existsSync(_distFragments) ? _distFragments : _srcFragments;
|
|
34447
34462
|
function createTemplateEngine(templatesDir, watch = false) {
|
|
34448
|
-
const env = import_nunjucks2.default.configure(templatesDir, {
|
|
34463
|
+
const env = import_nunjucks2.default.configure([templatesDir, BUNKI_FRAGMENTS_DIR], {
|
|
34449
34464
|
autoescape: true,
|
|
34450
34465
|
watch
|
|
34451
34466
|
});
|
|
@@ -34496,12 +34511,24 @@ class SiteGenerator {
|
|
|
34496
34511
|
async initialize() {
|
|
34497
34512
|
this.metrics.startStage("initialization");
|
|
34498
34513
|
console.log("Initializing site generator...");
|
|
34514
|
+
const flatAssetsDir = path10.join(process.cwd(), "content", "_assets");
|
|
34515
|
+
try {
|
|
34516
|
+
const stat = await import("fs/promises").then((m3) => m3.stat(flatAssetsDir));
|
|
34517
|
+
if (stat.isDirectory()) {
|
|
34518
|
+
throw new Error(`Build error: content/_assets/ must not exist.
|
|
34519
|
+
Images must be placed in content/{year}/_assets/ (e.g. content/2025/_assets/).
|
|
34520
|
+
Move any files from content/_assets/ into the correct year folder and retry.`);
|
|
34521
|
+
}
|
|
34522
|
+
} catch (err) {
|
|
34523
|
+
if (err.code !== "ENOENT")
|
|
34524
|
+
throw err;
|
|
34525
|
+
}
|
|
34499
34526
|
await ensureDir(this.options.outputDir);
|
|
34500
34527
|
if (this.options.config.noFollowExceptions) {
|
|
34501
34528
|
setNoFollowExceptions(this.options.config.noFollowExceptions);
|
|
34502
34529
|
}
|
|
34503
34530
|
let tagDescriptions = {};
|
|
34504
|
-
const tagsTomlPath =
|
|
34531
|
+
const tagsTomlPath = path10.join(process.cwd(), "src", "tags.toml");
|
|
34505
34532
|
const tagsTomlFile = Bun.file(tagsTomlPath);
|
|
34506
34533
|
if (await tagsTomlFile.exists()) {
|
|
34507
34534
|
try {
|
|
@@ -34563,8 +34590,8 @@ class SiteGenerator {
|
|
|
34563
34590
|
this.metrics.startStage("cssProcessing");
|
|
34564
34591
|
let cssChanged = true;
|
|
34565
34592
|
if (this.cache && this.incrementalMode && this.options.config.css) {
|
|
34566
|
-
const cssInputPath =
|
|
34567
|
-
const cssOutputPath =
|
|
34593
|
+
const cssInputPath = path10.resolve(process.cwd(), this.options.config.css.input);
|
|
34594
|
+
const cssOutputPath = path10.join(this.options.outputDir, this.options.config.css.output);
|
|
34568
34595
|
const cssOutputExists = await Bun.file(cssOutputPath).exists();
|
|
34569
34596
|
cssChanged = await hasFileChanged(cssInputPath, this.cache);
|
|
34570
34597
|
if (!cssChanged && cssOutputExists) {
|
|
@@ -34598,19 +34625,19 @@ class SiteGenerator {
|
|
|
34598
34625
|
}
|
|
34599
34626
|
async generateFeeds() {
|
|
34600
34627
|
const rssContent = generateRSSFeed(this.site, this.options.config);
|
|
34601
|
-
await Bun.write(
|
|
34628
|
+
await Bun.write(path10.join(this.options.outputDir, "feed.xml"), rssContent);
|
|
34602
34629
|
const sitemapContent = generateSitemap(this.site, this.options.config, PAGINATION.DEFAULT_PAGE_SIZE);
|
|
34603
|
-
await Bun.write(
|
|
34630
|
+
await Bun.write(path10.join(this.options.outputDir, "sitemap.xml"), sitemapContent);
|
|
34604
34631
|
console.log("Generated sitemap.xml");
|
|
34605
34632
|
const urlCount = this.site.posts.length + Object.keys(this.site.tags).length + 10;
|
|
34606
34633
|
const sitemapSize = sitemapContent.length;
|
|
34607
34634
|
if (urlCount > FILES.MAX_SITEMAP_URLS || sitemapSize > FILES.MAX_SITEMAP_SIZE) {
|
|
34608
34635
|
const sitemapIndexContent = generateSitemapIndex(this.options.config);
|
|
34609
|
-
await Bun.write(
|
|
34636
|
+
await Bun.write(path10.join(this.options.outputDir, "sitemap_index.xml"), sitemapIndexContent);
|
|
34610
34637
|
console.log("Generated sitemap_index.xml");
|
|
34611
34638
|
}
|
|
34612
34639
|
const robotsTxtContent = generateRobotsTxt(this.options.config);
|
|
34613
|
-
await Bun.write(
|
|
34640
|
+
await Bun.write(path10.join(this.options.outputDir, "robots.txt"), robotsTxtContent);
|
|
34614
34641
|
console.log("Generated robots.txt");
|
|
34615
34642
|
}
|
|
34616
34643
|
async parseContent() {
|
|
@@ -34627,7 +34654,7 @@ class SiteGenerator {
|
|
|
34627
34654
|
return posts;
|
|
34628
34655
|
}
|
|
34629
34656
|
const allFiles = await findFilesByPattern("**/*.md", this.options.contentDir, true);
|
|
34630
|
-
const configPath =
|
|
34657
|
+
const configPath = path10.join(process.cwd(), "bunki.config.ts");
|
|
34631
34658
|
const configChanged = await hasConfigChanged(configPath, this.cache);
|
|
34632
34659
|
if (configChanged) {
|
|
34633
34660
|
console.log("Config changed, full rebuild required");
|
|
@@ -34706,10 +34733,10 @@ var defaultDeps2 = {
|
|
|
34706
34733
|
};
|
|
34707
34734
|
async function handleGenerateCommand(options2, deps = defaultDeps2) {
|
|
34708
34735
|
try {
|
|
34709
|
-
const configPath =
|
|
34710
|
-
const contentDir =
|
|
34711
|
-
const outputDir =
|
|
34712
|
-
const templatesDir =
|
|
34736
|
+
const configPath = path11.resolve(options2.config);
|
|
34737
|
+
const contentDir = path11.resolve(options2.content);
|
|
34738
|
+
const outputDir = path11.resolve(options2.output);
|
|
34739
|
+
const templatesDir = path11.resolve(options2.templates);
|
|
34713
34740
|
deps.logger.log("Generating site with:");
|
|
34714
34741
|
deps.logger.log(`- Config file: ${configPath}`);
|
|
34715
34742
|
deps.logger.log(`- Content directory: ${contentDir}`);
|
|
@@ -34743,11 +34770,11 @@ function registerGenerateCommand(program2) {
|
|
|
34743
34770
|
}
|
|
34744
34771
|
|
|
34745
34772
|
// src/utils/image-uploader.ts
|
|
34746
|
-
import
|
|
34773
|
+
import path13 from "path";
|
|
34747
34774
|
|
|
34748
34775
|
// src/utils/s3-uploader.ts
|
|
34749
34776
|
var {S3Client } = globalThis.Bun;
|
|
34750
|
-
import
|
|
34777
|
+
import path12 from "path";
|
|
34751
34778
|
|
|
34752
34779
|
class S3Uploader {
|
|
34753
34780
|
s3Config;
|
|
@@ -34870,7 +34897,7 @@ class S3Uploader {
|
|
|
34870
34897
|
let failedCount = 0;
|
|
34871
34898
|
const uploadTasks = imageFiles.map((imageFile) => async () => {
|
|
34872
34899
|
try {
|
|
34873
|
-
const imagePath =
|
|
34900
|
+
const imagePath = path12.join(imagesDir, imageFile);
|
|
34874
34901
|
const s3Key = keyTransform ? keyTransform(imageFile) : imageFile;
|
|
34875
34902
|
const file = Bun.file(imagePath);
|
|
34876
34903
|
if (process.env.BUNKI_DRY_RUN === "true") {} else {
|
|
@@ -34904,13 +34931,13 @@ function createUploader(config) {
|
|
|
34904
34931
|
}
|
|
34905
34932
|
|
|
34906
34933
|
// src/utils/image-uploader.ts
|
|
34907
|
-
var DEFAULT_IMAGES_DIR =
|
|
34908
|
-
var DEFAULT_CONTENT_DIR2 =
|
|
34934
|
+
var DEFAULT_IMAGES_DIR = path13.join(process.cwd(), "assets");
|
|
34935
|
+
var DEFAULT_CONTENT_DIR2 = path13.join(process.cwd(), "content");
|
|
34909
34936
|
async function uploadImages(options2 = {}) {
|
|
34910
34937
|
try {
|
|
34911
34938
|
const contentAssetsMode = options2.contentAssets === true;
|
|
34912
34939
|
const defaultDir = contentAssetsMode ? DEFAULT_CONTENT_DIR2 : DEFAULT_IMAGES_DIR;
|
|
34913
|
-
const imagesDir =
|
|
34940
|
+
const imagesDir = path13.resolve(options2.images || defaultDir);
|
|
34914
34941
|
if (!await fileExists(imagesDir)) {
|
|
34915
34942
|
console.log(`Creating images directory at ${imagesDir}...`);
|
|
34916
34943
|
await ensureDir(imagesDir);
|
|
@@ -34962,7 +34989,7 @@ async function uploadImages(options2 = {}) {
|
|
|
34962
34989
|
const uploader = createUploader(s3Config);
|
|
34963
34990
|
const imageUrlMap = await uploader.uploadImages(imagesDir, options2.minYear, keyTransform);
|
|
34964
34991
|
if (options2.outputJson) {
|
|
34965
|
-
const outputFile =
|
|
34992
|
+
const outputFile = path13.resolve(options2.outputJson);
|
|
34966
34993
|
await Bun.write(outputFile, JSON.stringify(imageUrlMap, null, 2));
|
|
34967
34994
|
console.log(`Image URL mapping saved to ${outputFile}`);
|
|
34968
34995
|
}
|
|
@@ -35010,7 +35037,7 @@ function registerImagesPushCommand(program2) {
|
|
|
35010
35037
|
}
|
|
35011
35038
|
|
|
35012
35039
|
// src/cli/commands/init.ts
|
|
35013
|
-
import
|
|
35040
|
+
import path14 from "path";
|
|
35014
35041
|
var defaultDependencies = {
|
|
35015
35042
|
createDefaultConfig,
|
|
35016
35043
|
ensureDir,
|
|
@@ -35020,7 +35047,7 @@ var defaultDependencies = {
|
|
|
35020
35047
|
};
|
|
35021
35048
|
async function handleInitCommand(options2, deps = defaultDependencies) {
|
|
35022
35049
|
try {
|
|
35023
|
-
const configPath =
|
|
35050
|
+
const configPath = path14.resolve(options2.config);
|
|
35024
35051
|
const configCreated = await deps.createDefaultConfig(configPath);
|
|
35025
35052
|
if (!configCreated) {
|
|
35026
35053
|
deps.logger.log(`
|
|
@@ -35029,19 +35056,19 @@ Skipped initialization because the config file already exists`);
|
|
|
35029
35056
|
}
|
|
35030
35057
|
deps.logger.log("Creating directory structure...");
|
|
35031
35058
|
const baseDir = process.cwd();
|
|
35032
|
-
const contentDir =
|
|
35033
|
-
const templatesDir =
|
|
35034
|
-
const stylesDir =
|
|
35035
|
-
const publicDir =
|
|
35059
|
+
const contentDir = path14.join(baseDir, "content");
|
|
35060
|
+
const templatesDir = path14.join(baseDir, "templates");
|
|
35061
|
+
const stylesDir = path14.join(templatesDir, "styles");
|
|
35062
|
+
const publicDir = path14.join(baseDir, "public");
|
|
35036
35063
|
await deps.ensureDir(contentDir);
|
|
35037
35064
|
await deps.ensureDir(templatesDir);
|
|
35038
35065
|
await deps.ensureDir(stylesDir);
|
|
35039
35066
|
await deps.ensureDir(publicDir);
|
|
35040
35067
|
for (const [filename, content] of Object.entries(getDefaultTemplates())) {
|
|
35041
|
-
await deps.writeFile(
|
|
35068
|
+
await deps.writeFile(path14.join(templatesDir, filename), content);
|
|
35042
35069
|
}
|
|
35043
|
-
await deps.writeFile(
|
|
35044
|
-
await deps.writeFile(
|
|
35070
|
+
await deps.writeFile(path14.join(stylesDir, "main.css"), getDefaultCss());
|
|
35071
|
+
await deps.writeFile(path14.join(contentDir, "welcome.md"), getSamplePost());
|
|
35045
35072
|
deps.logger.log(`
|
|
35046
35073
|
Initialization complete! Here are the next steps:`);
|
|
35047
35074
|
deps.logger.log("1. Edit bunki.config.ts to configure your site");
|
|
@@ -35061,221 +35088,302 @@ function registerInitCommand(program2, deps = defaultDependencies) {
|
|
|
35061
35088
|
function getDefaultTemplates() {
|
|
35062
35089
|
return {
|
|
35063
35090
|
"base.njk": String.raw`<!DOCTYPE html>
|
|
35064
|
-
|
|
35065
|
-
|
|
35066
|
-
|
|
35067
|
-
|
|
35068
|
-
|
|
35069
|
-
|
|
35070
|
-
|
|
35071
|
-
|
|
35072
|
-
|
|
35073
|
-
|
|
35074
|
-
|
|
35075
|
-
|
|
35076
|
-
|
|
35077
|
-
|
|
35078
|
-
|
|
35079
|
-
|
|
35080
|
-
|
|
35081
|
-
|
|
35082
|
-
|
|
35083
|
-
|
|
35084
|
-
|
|
35085
|
-
|
|
35086
|
-
|
|
35087
|
-
|
|
35088
|
-
|
|
35089
|
-
|
|
35090
|
-
|
|
35091
|
-
|
|
35092
|
-
|
|
35093
|
-
|
|
35094
|
-
|
|
35095
|
-
|
|
35096
|
-
|
|
35091
|
+
<html lang="en">
|
|
35092
|
+
<head>
|
|
35093
|
+
<meta charset="UTF-8">
|
|
35094
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
35095
|
+
<title>{% block title %}{{ site.title }}{% endblock %}</title>
|
|
35096
|
+
<meta name="description" content="{% block description %}{{ site.description }}{% endblock %}">
|
|
35097
|
+
|
|
35098
|
+
{# Canonical URL #}
|
|
35099
|
+
<link rel="canonical" href="{% block canonical %}{{ site.baseUrl }}/{% endblock %}">
|
|
35100
|
+
|
|
35101
|
+
{# Open Graph meta tags #}
|
|
35102
|
+
<meta property="og:type" content="{% block og_type %}website{% endblock %}">
|
|
35103
|
+
<meta property="og:title" content="{% block og_title %}{{ site.title }}{% endblock %}">
|
|
35104
|
+
<meta property="og:description" content="{% block og_description %}{{ site.description }}{% endblock %}">
|
|
35105
|
+
<meta property="og:url" content="{% block og_url %}{{ site.baseUrl }}/{% endblock %}">
|
|
35106
|
+
<meta property="og:site_name" content="{{ site.title }}">
|
|
35107
|
+
{% block og_image %}{% endblock %}
|
|
35108
|
+
|
|
35109
|
+
{# Twitter Card meta tags #}
|
|
35110
|
+
<meta name="twitter:card" content="{% block twitter_card %}summary{% endblock %}">
|
|
35111
|
+
<meta name="twitter:title" content="{% block twitter_title %}{{ site.title }}{% endblock %}">
|
|
35112
|
+
<meta name="twitter:description" content="{% block twitter_description %}{{ site.description }}{% endblock %}">
|
|
35113
|
+
{% block twitter_image %}{% endblock %}
|
|
35114
|
+
|
|
35115
|
+
<link rel="stylesheet" href="/css/style.css">
|
|
35116
|
+
<link rel="alternate" type="application/rss+xml" title="{{ site.title }} RSS Feed" href="{{ site.baseUrl }}/feed.xml">
|
|
35117
|
+
{% block head %}{% endblock %}
|
|
35118
|
+
</head>
|
|
35119
|
+
<body>
|
|
35120
|
+
<header>
|
|
35121
|
+
<div class="container">
|
|
35122
|
+
<h1><a href="/">{{ site.title }}</a></h1>
|
|
35123
|
+
<nav>
|
|
35124
|
+
<ul>
|
|
35125
|
+
<li><a href="/">Home</a></li>
|
|
35126
|
+
<li><a href="/tags/">Tags</a></li>
|
|
35127
|
+
</ul>
|
|
35128
|
+
</nav>
|
|
35129
|
+
</div>
|
|
35130
|
+
</header>
|
|
35131
|
+
|
|
35132
|
+
<main class="container">
|
|
35133
|
+
{% block content %}{% endblock %}
|
|
35134
|
+
</main>
|
|
35135
|
+
|
|
35136
|
+
<footer>
|
|
35137
|
+
<div class="container">
|
|
35138
|
+
<p>© {{ "now" | date("YYYY") }} {{ site.title }} - Powered by <a href="https://github.com/kahwee/bunki">Bunki</a></p>
|
|
35139
|
+
</div>
|
|
35140
|
+
</footer>
|
|
35141
|
+
</body>
|
|
35142
|
+
</html>`,
|
|
35097
35143
|
"index.njk": String.raw`{% extends "base.njk" %}
|
|
35098
35144
|
|
|
35099
|
-
|
|
35100
|
-
|
|
35101
|
-
|
|
35102
|
-
|
|
35103
|
-
|
|
35104
|
-
|
|
35105
|
-
|
|
35106
|
-
|
|
35107
|
-
|
|
35108
|
-
|
|
35109
|
-
|
|
35110
|
-
|
|
35111
|
-
|
|
35112
|
-
|
|
35113
|
-
|
|
35114
|
-
|
|
35115
|
-
|
|
35116
|
-
|
|
35117
|
-
|
|
35118
|
-
|
|
35119
|
-
</
|
|
35120
|
-
|
|
35121
|
-
|
|
35122
|
-
|
|
35123
|
-
{%
|
|
35124
|
-
|
|
35125
|
-
|
|
35126
|
-
|
|
35127
|
-
|
|
35128
|
-
|
|
35129
|
-
{% if pagination.
|
|
35130
|
-
|
|
35131
|
-
|
|
35132
|
-
|
|
35133
|
-
|
|
35134
|
-
</
|
|
35135
|
-
|
|
35136
|
-
{% else %}
|
|
35137
|
-
<p>No posts yet!</p>
|
|
35145
|
+
{% block canonical %}{{ site.baseUrl }}/{% if pagination.currentPage > 1 %}page/{{ pagination.currentPage }}/{% endif %}{% endblock %}
|
|
35146
|
+
{% block og_url %}{{ site.baseUrl }}/{% if pagination.currentPage > 1 %}page/{{ pagination.currentPage }}/{% endif %}{% endblock %}
|
|
35147
|
+
|
|
35148
|
+
{% block content %}
|
|
35149
|
+
<h1>Latest Posts</h1>
|
|
35150
|
+
|
|
35151
|
+
{% if posts.length > 0 %}
|
|
35152
|
+
<div class="posts">
|
|
35153
|
+
{% for post in posts %}
|
|
35154
|
+
<article class="post-card">
|
|
35155
|
+
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
|
35156
|
+
<div class="post-meta">
|
|
35157
|
+
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
35158
|
+
{% if post.tags.length > 0 %}
|
|
35159
|
+
<span class="tags">
|
|
35160
|
+
{% for tag in post.tags %}
|
|
35161
|
+
<a href="/tags/{{ post.tagSlugs[tag] }}/">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
|
35162
|
+
{% endfor %}
|
|
35163
|
+
</span>
|
|
35164
|
+
{% endif %}
|
|
35165
|
+
</div>
|
|
35166
|
+
<div class="post-excerpt">{{ post.excerpt }}</div>
|
|
35167
|
+
<a href="{{ post.url }}" class="read-more">Read more \u2192</a>
|
|
35168
|
+
</article>
|
|
35169
|
+
{% endfor %}
|
|
35170
|
+
</div>
|
|
35171
|
+
|
|
35172
|
+
{% if pagination.totalPages > 1 %}
|
|
35173
|
+
<nav class="pagination">
|
|
35174
|
+
{% if pagination.hasPrevPage %}
|
|
35175
|
+
<a href="{{ pagination.pagePath }}{% if pagination.prevPage > 1 %}page/{{ pagination.prevPage }}/{% endif %}" class="prev">\u2190 Previous</a>
|
|
35176
|
+
{% endif %}
|
|
35177
|
+
{% if pagination.hasNextPage %}
|
|
35178
|
+
<a href="{{ pagination.pagePath }}page/{{ pagination.nextPage }}/" class="next">Next \u2192</a>
|
|
35179
|
+
{% endif %}
|
|
35180
|
+
<span class="page-info">Page {{ pagination.currentPage }} of {{ pagination.totalPages }}</span>
|
|
35181
|
+
</nav>
|
|
35138
35182
|
{% endif %}
|
|
35139
|
-
{%
|
|
35183
|
+
{% else %}
|
|
35184
|
+
<p>No posts yet.</p>
|
|
35185
|
+
{% endif %}
|
|
35186
|
+
{% endblock %}`,
|
|
35140
35187
|
"post.njk": String.raw`{% extends "base.njk" %}
|
|
35141
35188
|
|
|
35142
|
-
|
|
35143
|
-
|
|
35144
|
-
|
|
35145
|
-
|
|
35146
|
-
|
|
35147
|
-
|
|
35148
|
-
|
|
35149
|
-
|
|
35150
|
-
|
|
35151
|
-
|
|
35152
|
-
|
|
35153
|
-
|
|
35154
|
-
|
|
35155
|
-
|
|
35156
|
-
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
|
|
35160
|
-
|
|
35161
|
-
|
|
35162
|
-
|
|
35189
|
+
{% from "og-image.njk" import og_image, twitter_image %}
|
|
35190
|
+
{% from "json-ld.njk" import blog_posting_schema %}
|
|
35191
|
+
|
|
35192
|
+
{% block title %}{{ post.title }} | {{ site.title }}{% endblock %}
|
|
35193
|
+
{% block description %}{{ post.excerpt }}{% endblock %}
|
|
35194
|
+
|
|
35195
|
+
{% block canonical %}{{ site.baseUrl }}{{ post.url }}{% endblock %}
|
|
35196
|
+
|
|
35197
|
+
{% block og_type %}article{% endblock %}
|
|
35198
|
+
{% block og_title %}{{ post.title }}{% endblock %}
|
|
35199
|
+
{% block og_description %}{{ post.excerpt }}{% endblock %}
|
|
35200
|
+
{% block og_url %}{{ site.baseUrl }}{{ post.url }}{% endblock %}
|
|
35201
|
+
{% block og_image %}{{ og_image(post, site) }}{% endblock %}
|
|
35202
|
+
|
|
35203
|
+
{% block twitter_card %}summary_large_image{% endblock %}
|
|
35204
|
+
{% block twitter_title %}{{ post.title }}{% endblock %}
|
|
35205
|
+
{% block twitter_description %}{{ post.excerpt }}{% endblock %}
|
|
35206
|
+
{% block twitter_image %}{{ twitter_image(post, site) }}{% endblock %}
|
|
35207
|
+
|
|
35208
|
+
{% block head %}
|
|
35209
|
+
{{ blog_posting_schema(post, site) }}
|
|
35210
|
+
{% endblock %}
|
|
35211
|
+
|
|
35212
|
+
{% block content %}
|
|
35213
|
+
<article class="post">
|
|
35214
|
+
<header class="post-header">
|
|
35215
|
+
<h1>{{ post.title }}</h1>
|
|
35216
|
+
<div class="post-meta">
|
|
35217
|
+
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
35218
|
+
{% if post.tags.length > 0 %}
|
|
35219
|
+
<span class="tags">
|
|
35220
|
+
{% for tag in post.tags %}
|
|
35221
|
+
<a href="/tags/{{ post.tagSlugs[tag] }}/">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
|
35222
|
+
{% endfor %}
|
|
35223
|
+
</span>
|
|
35224
|
+
{% endif %}
|
|
35163
35225
|
</div>
|
|
35164
|
-
</
|
|
35165
|
-
{% endblock %}`,
|
|
35166
|
-
"tag.njk": String.raw`{% extends "base.njk" %}
|
|
35167
|
-
|
|
35168
|
-
{% block title %}{{ tag.name }} | {{ site.title }}{% endblock %}
|
|
35169
|
-
{% block description %}Posts tagged with {{ tag.name }} on {{ site.title }}{% endblock %}
|
|
35170
|
-
|
|
35171
|
-
{% block content %}
|
|
35172
|
-
<h1>Posts tagged "{{ tag.name }}"</h1>
|
|
35173
|
-
|
|
35174
|
-
{% if tag.description %}
|
|
35175
|
-
<div class="tag-description">{{ tag.description }}</div>
|
|
35176
|
-
{% endif %}
|
|
35226
|
+
</header>
|
|
35177
35227
|
|
|
35178
|
-
|
|
35179
|
-
|
|
35180
|
-
|
|
35181
|
-
|
|
35182
|
-
|
|
35183
|
-
|
|
35184
|
-
|
|
35185
|
-
|
|
35186
|
-
|
|
35187
|
-
|
|
35188
|
-
|
|
35189
|
-
|
|
35228
|
+
<div class="post-content">
|
|
35229
|
+
{{ post.html | safe }}
|
|
35230
|
+
</div>
|
|
35231
|
+
|
|
35232
|
+
<footer class="post-footer">
|
|
35233
|
+
<div class="share-buttons">
|
|
35234
|
+
<span class="share-label">Share:</span>
|
|
35235
|
+
<a href="https://twitter.com/intent/tweet?text={{ post.title | urlencode }}&url={{ site.baseUrl }}{{ post.url }}" target="_blank" rel="noopener noreferrer" class="share-button x" aria-label="Share on X">
|
|
35236
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
|
|
35237
|
+
</a>
|
|
35238
|
+
<a href="https://www.facebook.com/sharer/sharer.php?u={{ site.baseUrl }}{{ post.url }}" target="_blank" rel="noopener noreferrer" class="share-button facebook" aria-label="Share on Facebook">
|
|
35239
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 3.667h-3.533v7.98H9.101z"/></svg>
|
|
35240
|
+
</a>
|
|
35241
|
+
<a href="https://www.linkedin.com/sharing/share-offsite/?url={{ site.baseUrl }}{{ post.url }}" target="_blank" rel="noopener noreferrer" class="share-button linkedin" aria-label="Share on LinkedIn">
|
|
35242
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M6.5 21.5h-5v-13h5v13zM4 6.5C2.5 6.5 1.5 5.3 1.5 4s1-2.4 2.5-2.4c1.6 0 2.5 1 2.6 2.5 0 1.4-1 2.5-2.6 2.5zm11.5 6c-1 0-2 1-2 2v7h-5v-13h5V10s1.6-1.5 4-1.5c3 0 5 2.2 5 6.3v6.7h-5v-7c0-1-1-2-2-2z"/></svg>
|
|
35243
|
+
</a>
|
|
35244
|
+
<a href="mailto:?subject={{ post.title | urlencode }}&body=Check%20out%20this%20article%3A%20{{ site.baseUrl }}{{ post.url }}" class="share-button email" aria-label="Share via Email">
|
|
35245
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
|
|
35246
|
+
</a>
|
|
35190
35247
|
</div>
|
|
35248
|
+
</footer>
|
|
35249
|
+
</article>
|
|
35250
|
+
{% endblock %}`,
|
|
35251
|
+
"tag.njk": String.raw`{% extends "base.njk" %}
|
|
35191
35252
|
|
|
35192
|
-
|
|
35193
|
-
|
|
35194
|
-
|
|
35195
|
-
|
|
35196
|
-
|
|
35197
|
-
|
|
35198
|
-
|
|
35199
|
-
|
|
35200
|
-
|
|
35201
|
-
|
|
35202
|
-
|
|
35203
|
-
|
|
35204
|
-
|
|
35205
|
-
|
|
35206
|
-
|
|
35253
|
+
{% block title %}{{ tag.name }} | {{ site.title }}{% endblock %}
|
|
35254
|
+
{% block description %}Posts tagged with {{ tag.name }} on {{ site.title }}{% endblock %}
|
|
35255
|
+
|
|
35256
|
+
{% block canonical %}{{ site.baseUrl }}/tags/{{ tag.slug }}/{% if pagination.currentPage > 1 %}page/{{ pagination.currentPage }}/{% endif %}{% endblock %}
|
|
35257
|
+
|
|
35258
|
+
{% block og_title %}{{ tag.name }} | {{ site.title }}{% endblock %}
|
|
35259
|
+
{% block og_description %}Posts tagged with {{ tag.name }} on {{ site.title }}{% endblock %}
|
|
35260
|
+
{% block og_url %}{{ site.baseUrl }}/tags/{{ tag.slug }}/{% if pagination.currentPage > 1 %}page/{{ pagination.currentPage }}/{% endif %}{% endblock %}
|
|
35261
|
+
|
|
35262
|
+
{% block twitter_title %}{{ tag.name }} | {{ site.title }}{% endblock %}
|
|
35263
|
+
{% block twitter_description %}Posts tagged with {{ tag.name }} on {{ site.title }}{% endblock %}
|
|
35264
|
+
|
|
35265
|
+
{% block content %}
|
|
35266
|
+
<h1>Posts tagged "{{ tag.name }}"</h1>
|
|
35267
|
+
|
|
35268
|
+
{% if tag.description %}
|
|
35269
|
+
<div class="tag-description">{{ tag.description }}</div>
|
|
35270
|
+
{% endif %}
|
|
35271
|
+
|
|
35272
|
+
{% if tag.posts.length > 0 %}
|
|
35273
|
+
<div class="posts">
|
|
35274
|
+
{% for post in tag.posts %}
|
|
35275
|
+
<article class="post-card">
|
|
35276
|
+
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
|
35277
|
+
<div class="post-meta">
|
|
35278
|
+
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
35279
|
+
</div>
|
|
35280
|
+
<div class="post-excerpt">{{ post.excerpt }}</div>
|
|
35281
|
+
<a href="{{ post.url }}" class="read-more">Read more \u2192</a>
|
|
35282
|
+
</article>
|
|
35283
|
+
{% endfor %}
|
|
35284
|
+
</div>
|
|
35285
|
+
|
|
35286
|
+
{% if pagination.totalPages > 1 %}
|
|
35287
|
+
<nav class="pagination">
|
|
35288
|
+
{% if pagination.hasPrevPage %}
|
|
35289
|
+
<a href="{{ pagination.pagePath }}{% if pagination.prevPage > 1 %}page/{{ pagination.prevPage }}/{% endif %}" class="prev">\u2190 Previous</a>
|
|
35290
|
+
{% endif %}
|
|
35291
|
+
{% if pagination.hasNextPage %}
|
|
35292
|
+
<a href="{{ pagination.pagePath }}page/{{ pagination.nextPage }}/" class="next">Next \u2192</a>
|
|
35293
|
+
{% endif %}
|
|
35294
|
+
<span class="page-info">Page {{ pagination.currentPage }} of {{ pagination.totalPages }}</span>
|
|
35295
|
+
</nav>
|
|
35207
35296
|
{% endif %}
|
|
35208
|
-
{%
|
|
35297
|
+
{% else %}
|
|
35298
|
+
<p>No posts with this tag yet.</p>
|
|
35299
|
+
{% endif %}
|
|
35300
|
+
{% endblock %}`,
|
|
35209
35301
|
"tags.njk": String.raw`{% extends "base.njk" %}
|
|
35210
35302
|
|
|
35211
|
-
|
|
35212
|
-
|
|
35303
|
+
{% block title %}Tags | {{ site.title }}{% endblock %}
|
|
35304
|
+
{% block description %}Browse all tags on {{ site.title }}{% endblock %}
|
|
35213
35305
|
|
|
35214
|
-
|
|
35215
|
-
<h1>All Tags</h1>
|
|
35306
|
+
{% block canonical %}{{ site.baseUrl }}/tags/{% endblock %}
|
|
35216
35307
|
|
|
35217
|
-
|
|
35218
|
-
|
|
35219
|
-
|
|
35220
|
-
<li>
|
|
35221
|
-
<a href="/tags/{{ tag.slug }}/">{{ tag.name }}</a>
|
|
35222
|
-
<span class="count">({{ tag.count }})</span>
|
|
35223
|
-
{% if tag.description %}
|
|
35224
|
-
<p class="description">{{ tag.description }}</p>
|
|
35225
|
-
{% endif %}
|
|
35226
|
-
</li>
|
|
35227
|
-
{% endfor %}
|
|
35228
|
-
</ul>
|
|
35229
|
-
{% else %}
|
|
35230
|
-
<p>No tags found!</p>
|
|
35231
|
-
{% endif %}
|
|
35232
|
-
{% endblock %}`,
|
|
35233
|
-
"archive.njk": String.raw`{% extends "base.njk" %}
|
|
35308
|
+
{% block og_title %}Tags | {{ site.title }}{% endblock %}
|
|
35309
|
+
{% block og_description %}Browse all tags on {{ site.title }}{% endblock %}
|
|
35310
|
+
{% block og_url %}{{ site.baseUrl }}/tags/{% endblock %}
|
|
35234
35311
|
|
|
35235
|
-
|
|
35236
|
-
|
|
35237
|
-
|
|
35238
|
-
{% block content %}
|
|
35239
|
-
<h1>Posts from {{ year }}</h1>
|
|
35240
|
-
|
|
35241
|
-
{% if posts.length > 0 %}
|
|
35242
|
-
<div class="posts">
|
|
35243
|
-
{% for post in posts %}
|
|
35244
|
-
<article class="post-card">
|
|
35245
|
-
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
|
35246
|
-
<div class="post-meta">
|
|
35247
|
-
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
35248
|
-
{% if post.tags.length > 0 %}
|
|
35249
|
-
<span class="tags">
|
|
35250
|
-
{% for tag in post.tags %}
|
|
35251
|
-
<a href="/tags/{{ post.tagSlugs[tag] }}/">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
|
35252
|
-
{% endfor %}
|
|
35253
|
-
</span>
|
|
35254
|
-
{% endif %}
|
|
35255
|
-
</div>
|
|
35256
|
-
<div class="post-excerpt">{{ post.excerpt }}</div>
|
|
35257
|
-
<a href="{{ post.url }}" class="read-more">Read more \u2192</a>
|
|
35258
|
-
</article>
|
|
35259
|
-
{% endfor %}
|
|
35260
|
-
</div>
|
|
35312
|
+
{% block twitter_title %}Tags | {{ site.title }}{% endblock %}
|
|
35313
|
+
{% block twitter_description %}Browse all tags on {{ site.title }}{% endblock %}
|
|
35261
35314
|
|
|
35262
|
-
|
|
35263
|
-
|
|
35264
|
-
{% if pagination.hasPrevPage %}
|
|
35265
|
-
<a href="/{{ year }}/{% if pagination.prevPage > 1 %}page/{{ pagination.prevPage }}/{% endif %}" class="prev">\u2190 Previous</a>
|
|
35266
|
-
{% endif %}
|
|
35315
|
+
{% block content %}
|
|
35316
|
+
<h1>All Tags</h1>
|
|
35267
35317
|
|
|
35268
|
-
|
|
35269
|
-
|
|
35318
|
+
{% if tags.length > 0 %}
|
|
35319
|
+
<ul class="tags-list">
|
|
35320
|
+
{% for tag in tags %}
|
|
35321
|
+
<li>
|
|
35322
|
+
<a href="/tags/{{ tag.slug }}/">{{ tag.name }}</a>
|
|
35323
|
+
<span class="count">({{ tag.count }})</span>
|
|
35324
|
+
{% if tag.description %}
|
|
35325
|
+
<p class="description">{{ tag.description }}</p>
|
|
35270
35326
|
{% endif %}
|
|
35327
|
+
</li>
|
|
35328
|
+
{% endfor %}
|
|
35329
|
+
</ul>
|
|
35330
|
+
{% else %}
|
|
35331
|
+
<p>No tags yet.</p>
|
|
35332
|
+
{% endif %}
|
|
35333
|
+
{% endblock %}`,
|
|
35334
|
+
"archive.njk": String.raw`{% extends "base.njk" %}
|
|
35271
35335
|
|
|
35272
|
-
|
|
35273
|
-
|
|
35274
|
-
|
|
35275
|
-
|
|
35276
|
-
|
|
35336
|
+
{% block title %}Archive {{ year }} | {{ site.title }}{% endblock %}
|
|
35337
|
+
{% block description %}Posts from {{ year }} on {{ site.title }}{% endblock %}
|
|
35338
|
+
|
|
35339
|
+
{% block canonical %}{{ site.baseUrl }}/{{ year }}/{% if pagination.currentPage > 1 %}page/{{ pagination.currentPage }}/{% endif %}{% endblock %}
|
|
35340
|
+
|
|
35341
|
+
{% block og_title %}Archive {{ year }} | {{ site.title }}{% endblock %}
|
|
35342
|
+
{% block og_description %}Posts from {{ year }} on {{ site.title }}{% endblock %}
|
|
35343
|
+
{% block og_url %}{{ site.baseUrl }}/{{ year }}/{% if pagination.currentPage > 1 %}page/{{ pagination.currentPage }}/{% endif %}{% endblock %}
|
|
35344
|
+
|
|
35345
|
+
{% block twitter_title %}Archive {{ year }} | {{ site.title }}{% endblock %}
|
|
35346
|
+
{% block twitter_description %}Posts from {{ year }} on {{ site.title }}{% endblock %}
|
|
35347
|
+
|
|
35348
|
+
{% block content %}
|
|
35349
|
+
<h1>Posts from {{ year }}</h1>
|
|
35350
|
+
|
|
35351
|
+
{% if posts.length > 0 %}
|
|
35352
|
+
<div class="posts">
|
|
35353
|
+
{% for post in posts %}
|
|
35354
|
+
<article class="post-card">
|
|
35355
|
+
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
|
|
35356
|
+
<div class="post-meta">
|
|
35357
|
+
<time datetime="{{ post.date }}">{{ post.date | date("MMMM D, YYYY") }}</time>
|
|
35358
|
+
{% if post.tags.length > 0 %}
|
|
35359
|
+
<span class="tags">
|
|
35360
|
+
{% for tag in post.tags %}
|
|
35361
|
+
<a href="/tags/{{ post.tagSlugs[tag] }}/">{{ tag }}</a>{% if not loop.last %}, {% endif %}
|
|
35362
|
+
{% endfor %}
|
|
35363
|
+
</span>
|
|
35364
|
+
{% endif %}
|
|
35365
|
+
</div>
|
|
35366
|
+
<div class="post-excerpt">{{ post.excerpt }}</div>
|
|
35367
|
+
<a href="{{ post.url }}" class="read-more">Read more \u2192</a>
|
|
35368
|
+
</article>
|
|
35369
|
+
{% endfor %}
|
|
35370
|
+
</div>
|
|
35371
|
+
|
|
35372
|
+
{% if pagination.totalPages > 1 %}
|
|
35373
|
+
<nav class="pagination">
|
|
35374
|
+
{% if pagination.hasPrevPage %}
|
|
35375
|
+
<a href="/{{ year }}/{% if pagination.prevPage > 1 %}page/{{ pagination.prevPage }}/{% endif %}" class="prev">\u2190 Previous</a>
|
|
35376
|
+
{% endif %}
|
|
35377
|
+
{% if pagination.hasNextPage %}
|
|
35378
|
+
<a href="/{{ year }}/page/{{ pagination.nextPage }}/" class="next">Next \u2192</a>
|
|
35379
|
+
{% endif %}
|
|
35380
|
+
<span class="page-info">Page {{ pagination.currentPage }} of {{ pagination.totalPages }}</span>
|
|
35381
|
+
</nav>
|
|
35277
35382
|
{% endif %}
|
|
35278
|
-
{%
|
|
35383
|
+
{% else %}
|
|
35384
|
+
<p>No posts from {{ year }} yet.</p>
|
|
35385
|
+
{% endif %}
|
|
35386
|
+
{% endblock %}`
|
|
35279
35387
|
};
|
|
35280
35388
|
}
|
|
35281
35389
|
function getDefaultCss() {
|
|
@@ -35470,6 +35578,43 @@ function getDefaultCss() {
|
|
|
35470
35578
|
font-size: 0.9rem;
|
|
35471
35579
|
}
|
|
35472
35580
|
|
|
35581
|
+
/* Share buttons */
|
|
35582
|
+
.post-footer {
|
|
35583
|
+
margin-top: 2rem;
|
|
35584
|
+
padding-top: 1.5rem;
|
|
35585
|
+
border-top: 1px solid #eee;
|
|
35586
|
+
}
|
|
35587
|
+
|
|
35588
|
+
.share-buttons {
|
|
35589
|
+
display: flex;
|
|
35590
|
+
align-items: center;
|
|
35591
|
+
gap: 0.75rem;
|
|
35592
|
+
}
|
|
35593
|
+
|
|
35594
|
+
.share-label {
|
|
35595
|
+
font-size: 0.9rem;
|
|
35596
|
+
font-weight: 500;
|
|
35597
|
+
color: #6c757d;
|
|
35598
|
+
}
|
|
35599
|
+
|
|
35600
|
+
.share-button {
|
|
35601
|
+
display: inline-flex;
|
|
35602
|
+
align-items: center;
|
|
35603
|
+
justify-content: center;
|
|
35604
|
+
width: 2.25rem;
|
|
35605
|
+
height: 2.25rem;
|
|
35606
|
+
border-radius: 50%;
|
|
35607
|
+
background-color: #f5f5f5;
|
|
35608
|
+
color: #555;
|
|
35609
|
+
transition: background-color 0.2s, color 0.2s;
|
|
35610
|
+
}
|
|
35611
|
+
|
|
35612
|
+
.share-button:hover { text-decoration: none; }
|
|
35613
|
+
.share-button.x:hover { background-color: #000; color: #fff; }
|
|
35614
|
+
.share-button.facebook:hover { background-color: #1877f2; color: #fff; }
|
|
35615
|
+
.share-button.linkedin:hover { background-color: #0077b5; color: #fff; }
|
|
35616
|
+
.share-button.email:hover { background-color: #6c757d; color: #fff; }
|
|
35617
|
+
|
|
35473
35618
|
/* Footer */
|
|
35474
35619
|
footer {
|
|
35475
35620
|
text-align: center;
|
|
@@ -35533,7 +35678,7 @@ function hello() {
|
|
|
35533
35678
|
}
|
|
35534
35679
|
|
|
35535
35680
|
// src/cli/commands/new-post.ts
|
|
35536
|
-
import
|
|
35681
|
+
import path15 from "path";
|
|
35537
35682
|
var defaultDeps4 = {
|
|
35538
35683
|
writeFile: (filePath, data) => Bun.write(filePath, data),
|
|
35539
35684
|
now: () => new Date,
|
|
@@ -35557,7 +35702,7 @@ async function handleNewCommand(title, options2, deps = defaultDeps4) {
|
|
|
35557
35702
|
` + `# ${title}
|
|
35558
35703
|
|
|
35559
35704
|
`;
|
|
35560
|
-
const filePath =
|
|
35705
|
+
const filePath = path15.join(DEFAULT_CONTENT_DIR, `${slug}.md`);
|
|
35561
35706
|
await deps.writeFile(filePath, frontmatter);
|
|
35562
35707
|
deps.logger.log(`Created new post: ${filePath}`);
|
|
35563
35708
|
return filePath;
|
|
@@ -35574,11 +35719,11 @@ function registerNewCommand(program2) {
|
|
|
35574
35719
|
}
|
|
35575
35720
|
|
|
35576
35721
|
// src/cli/commands/serve.ts
|
|
35577
|
-
import
|
|
35722
|
+
import path17 from "path";
|
|
35578
35723
|
|
|
35579
35724
|
// src/server.ts
|
|
35580
35725
|
import fs2 from "fs";
|
|
35581
|
-
import
|
|
35726
|
+
import path16 from "path";
|
|
35582
35727
|
async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
35583
35728
|
try {
|
|
35584
35729
|
const stats = await fs2.promises.stat(outputDir);
|
|
@@ -35613,18 +35758,18 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
|
35613
35758
|
let filePath = "";
|
|
35614
35759
|
if (homePaginationMatch) {
|
|
35615
35760
|
const pageNumber = homePaginationMatch[1];
|
|
35616
|
-
filePath =
|
|
35761
|
+
filePath = path16.join(outputDir, "page", pageNumber, "index.html");
|
|
35617
35762
|
} else if (tagPaginationMatch) {
|
|
35618
35763
|
const tagSlug = tagPaginationMatch[1];
|
|
35619
35764
|
const pageNumber = tagPaginationMatch[2];
|
|
35620
|
-
filePath =
|
|
35765
|
+
filePath = path16.join(outputDir, "tags", tagSlug, "page", pageNumber, "index.html");
|
|
35621
35766
|
} else if (yearPaginationMatch) {
|
|
35622
35767
|
const year = yearPaginationMatch[1];
|
|
35623
35768
|
const pageNumber = yearPaginationMatch[2];
|
|
35624
|
-
filePath =
|
|
35769
|
+
filePath = path16.join(outputDir, year, "page", pageNumber, "index.html");
|
|
35625
35770
|
} else {
|
|
35626
|
-
const directPath =
|
|
35627
|
-
const withoutSlash =
|
|
35771
|
+
const directPath = path16.join(outputDir, pathname);
|
|
35772
|
+
const withoutSlash = path16.join(outputDir, pathname + ".html");
|
|
35628
35773
|
const withHtml = pathname.endsWith(".html") ? directPath : withoutSlash;
|
|
35629
35774
|
const bunFileDirect = Bun.file(directPath);
|
|
35630
35775
|
const bunFileHtml = Bun.file(withHtml);
|
|
@@ -35633,7 +35778,7 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
|
35633
35778
|
} else if (await bunFileHtml.exists()) {
|
|
35634
35779
|
filePath = withHtml;
|
|
35635
35780
|
} else {
|
|
35636
|
-
const indexPath =
|
|
35781
|
+
const indexPath = path16.join(outputDir, pathname, "index.html");
|
|
35637
35782
|
const bunFileIndex = Bun.file(indexPath);
|
|
35638
35783
|
if (await bunFileIndex.exists()) {
|
|
35639
35784
|
filePath = indexPath;
|
|
@@ -35647,7 +35792,7 @@ async function startServer(outputDir = DEFAULT_OUTPUT_DIR, port = 3000) {
|
|
|
35647
35792
|
}
|
|
35648
35793
|
}
|
|
35649
35794
|
console.log(`Serving file: ${filePath}`);
|
|
35650
|
-
const extname =
|
|
35795
|
+
const extname = path16.extname(filePath);
|
|
35651
35796
|
let contentType = "text/html";
|
|
35652
35797
|
switch (extname) {
|
|
35653
35798
|
case ".js":
|
|
@@ -35706,7 +35851,7 @@ var defaultDeps5 = {
|
|
|
35706
35851
|
};
|
|
35707
35852
|
async function handleServeCommand(options2, deps = defaultDeps5) {
|
|
35708
35853
|
try {
|
|
35709
|
-
const outputDir =
|
|
35854
|
+
const outputDir = path17.resolve(options2.output);
|
|
35710
35855
|
const port = parseInt(options2.port, 10);
|
|
35711
35856
|
await deps.startServer(outputDir, port);
|
|
35712
35857
|
} catch (error) {
|
|
@@ -35749,7 +35894,7 @@ function registerValidateCommand(program2) {
|
|
|
35749
35894
|
}
|
|
35750
35895
|
|
|
35751
35896
|
// src/cli/commands/validate-media.ts
|
|
35752
|
-
import { readdirSync, readFileSync, existsSync, statSync } from "fs";
|
|
35897
|
+
import { readdirSync, readFileSync, existsSync as existsSync2, statSync } from "fs";
|
|
35753
35898
|
import { join, dirname, resolve, basename } from "path";
|
|
35754
35899
|
var imageExtensions = [".jpg", ".jpeg", ".png", ".webp", ".gif"];
|
|
35755
35900
|
var videoExtensions = [".mp4", ".webm", ".mov"];
|
|
@@ -35757,7 +35902,7 @@ var mediaExtensions = [...imageExtensions, ...videoExtensions];
|
|
|
35757
35902
|
async function handleValidateMediaCommand(options2, deps = { logger: console, exit: (code) => process.exit(code) }) {
|
|
35758
35903
|
const contentDir = options2.contentDir || join(process.cwd(), "content");
|
|
35759
35904
|
const assetsDir = join(process.cwd(), "assets");
|
|
35760
|
-
if (!
|
|
35905
|
+
if (!existsSync2(contentDir)) {
|
|
35761
35906
|
deps.logger.error(`Content directory not found: ${contentDir}`);
|
|
35762
35907
|
deps.exit(1);
|
|
35763
35908
|
}
|
|
@@ -35879,7 +36024,7 @@ function validateMedia(contentDir, assetsDir) {
|
|
|
35879
36024
|
}
|
|
35880
36025
|
function getAllMediaFromContentAssets(contentDir) {
|
|
35881
36026
|
const mediaFiles = [];
|
|
35882
|
-
if (!
|
|
36027
|
+
if (!existsSync2(contentDir))
|
|
35883
36028
|
return [];
|
|
35884
36029
|
const years = readdirSync(contentDir).filter((f) => {
|
|
35885
36030
|
const fullPath = join(contentDir, f);
|
|
@@ -35887,7 +36032,7 @@ function getAllMediaFromContentAssets(contentDir) {
|
|
|
35887
36032
|
});
|
|
35888
36033
|
for (const year of years) {
|
|
35889
36034
|
const assetsDir = join(contentDir, year, "_assets");
|
|
35890
|
-
if (!
|
|
36035
|
+
if (!existsSync2(assetsDir))
|
|
35891
36036
|
continue;
|
|
35892
36037
|
const files = readdirSync(assetsDir);
|
|
35893
36038
|
for (const file of files) {
|
|
@@ -35909,7 +36054,7 @@ function getAllMediaFromContentAssets(contentDir) {
|
|
|
35909
36054
|
}
|
|
35910
36055
|
function getAllMediaFromAssets(assetsDir) {
|
|
35911
36056
|
const mediaFiles = [];
|
|
35912
|
-
if (!
|
|
36057
|
+
if (!existsSync2(assetsDir))
|
|
35913
36058
|
return [];
|
|
35914
36059
|
const years = readdirSync(assetsDir).filter((f) => {
|
|
35915
36060
|
const fullPath = join(assetsDir, f);
|
|
@@ -35941,7 +36086,7 @@ function getAllMediaFromAssets(assetsDir) {
|
|
|
35941
36086
|
function checkMediaReference(markdownFile, lineNumber, mediaPath, type, missingReferences) {
|
|
35942
36087
|
const markdownDir = dirname(markdownFile);
|
|
35943
36088
|
const resolvedPath = resolve(markdownDir, mediaPath);
|
|
35944
|
-
if (!
|
|
36089
|
+
if (!existsSync2(resolvedPath)) {
|
|
35945
36090
|
missingReferences.push({
|
|
35946
36091
|
file: markdownFile.replace(process.cwd() + "/", ""),
|
|
35947
36092
|
line: lineNumber,
|