bunki 0.16.2 → 0.17.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.
@@ -1,33 +1,33 @@
1
- import { GeneratorOptions } from "./types";
1
+ /**
2
+ * Site generator orchestrator
3
+ * Coordinates all generation tasks using modular generators
4
+ */
5
+ import type { GeneratorOptions } from "./types";
2
6
  export declare class SiteGenerator {
3
7
  private options;
4
8
  private site;
5
- private formatRSSDate;
6
- private groupPostsByYear;
7
- private getSortedTags;
8
- private createPagination;
9
+ private metrics;
9
10
  constructor(options: GeneratorOptions);
11
+ /**
12
+ * Initialize site data - parse markdown and prepare site structure
13
+ */
10
14
  initialize(): Promise<void>;
15
+ /**
16
+ * Generate all static site content
17
+ */
11
18
  generate(): Promise<void>;
12
- private generate404Page;
13
- private generateYearArchives;
14
- private generateIndexPage;
15
- private generatePostPages;
16
- private generateTagPages;
17
- private generateMapPage;
18
- private generateStylesheet;
19
- private fallbackCSSGeneration;
20
- private copyStaticAssets;
21
19
  /**
22
- * Extract the first image URL from HTML content
20
+ * Generate all feed files (RSS, sitemap, robots.txt)
21
+ */
22
+ private generateFeeds;
23
+ /**
24
+ * Group posts by year (Pacific timezone)
25
+ * @param posts - Array of posts
26
+ * @returns Posts grouped by year
23
27
  */
24
- private extractFirstImageUrl;
28
+ private groupPostsByYear;
25
29
  /**
26
- * Escape special characters in XML text to prevent CDATA issues
30
+ * Calculate output statistics (file count and total size)
27
31
  */
28
- private escapeXml;
29
- private generateRSSFeed;
30
- private generateSitemap;
31
- private generateSitemapIndex;
32
- private generateRobotsTxt;
32
+ private calculateOutputStats;
33
33
  }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Build performance metrics and tracking
3
+ */
4
+ export interface BuildMetrics {
5
+ totalTime: number;
6
+ stages: {
7
+ initialization: number;
8
+ cssProcessing: number;
9
+ pageGeneration: number;
10
+ feedGeneration: number;
11
+ assetCopying: number;
12
+ };
13
+ outputs: {
14
+ posts: number;
15
+ pages: number;
16
+ totalSize: number;
17
+ };
18
+ }
19
+ export interface BuildStage {
20
+ name: keyof BuildMetrics["stages"];
21
+ startTime: number;
22
+ }
23
+ export declare class MetricsCollector {
24
+ private startTime;
25
+ private stageTimings;
26
+ private currentStage;
27
+ constructor();
28
+ /**
29
+ * Start tracking a build stage
30
+ */
31
+ startStage(name: keyof BuildMetrics["stages"]): void;
32
+ /**
33
+ * End the current build stage
34
+ */
35
+ endStage(): void;
36
+ /**
37
+ * Get final build metrics
38
+ */
39
+ getMetrics(outputs: {
40
+ posts: number;
41
+ pages: number;
42
+ totalSize: number;
43
+ }): BuildMetrics;
44
+ }
45
+ /**
46
+ * Format bytes to human-readable string
47
+ */
48
+ export declare function formatBytes(bytes: number): string;
49
+ /**
50
+ * Display build metrics in the console
51
+ */
52
+ export declare function displayMetrics(metrics: BuildMetrics): void;
@@ -8,12 +8,21 @@ export interface CSSProcessorOptions {
8
8
  outputDir: string;
9
9
  /** Whether to run in verbose mode */
10
10
  verbose?: boolean;
11
+ /** Enable content-based cache busting with hash */
12
+ enableHashing?: boolean;
13
+ }
14
+ export interface CSSProcessResult {
15
+ /** Output file path (may include hash if hashing enabled) */
16
+ outputPath: string;
17
+ /** Content hash (8-char base36) if hashing enabled */
18
+ hash?: string;
11
19
  }
12
20
  /**
13
21
  * Process CSS using PostCSS directly
14
22
  * Throws on error - no fallback
23
+ * Returns output path (with hash if hashing enabled)
15
24
  */
16
- export declare function processCSS(options: CSSProcessorOptions): Promise<void>;
25
+ export declare function processCSS(options: CSSProcessorOptions): Promise<CSSProcessResult>;
17
26
  /**
18
27
  * Watch CSS files for changes and reprocess
19
28
  */
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Constants for markdown processing
3
+ * Extracted for better code organization and performance
4
+ */
5
+ export declare const RELATIVE_LINK_REGEX: RegExp;
6
+ export declare const IMAGE_PATH_REGEX: RegExp;
7
+ export declare const YOUTUBE_EMBED_REGEX: RegExp;
8
+ export declare const EXTERNAL_LINK_REGEX: RegExp;
9
+ export declare const SCHEMA_ORG_PLACE_TYPES: Set<string>;
10
+ export declare const ALERT_ICONS: {
11
+ readonly note: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0Zm-7-4a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM9 9a.75.75 0 0 0 0 1.5h.253a.25.25 0 0 1 .244.304l-.459 2.066A1.75 1.75 0 0 0 10.747 15H11a.75.75 0 0 0 0-1.5h-.253a.25.25 0 0 1-.244-.304l.459-2.066A1.75 1.75 0 0 0 9.253 9H9Z\" clip-rule=\"evenodd\" /></svg>";
12
+ readonly tip: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M10 1a6 6 0 0 0-3.815 10.631C7.237 12.5 8 13.443 8 14.456v.644a.75.75 0 0 0 .75.75h2.5a.75.75 0 0 0 .75-.75v-.644c0-1.013.762-1.957 1.815-2.825A6 6 0 0 0 10 1ZM8.863 17.414a.75.75 0 0 0-.226 1.483 9.066 9.066 0 0 0 2.726 0 .75.75 0 0 0-.226-1.483 7.553 7.553 0 0 1-2.274 0Z\" /></svg>";
13
+ readonly important: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0Zm-8-5a.75.75 0 0 1 .75.75v4.5a.75.75 0 0 1-1.5 0v-4.5A.75.75 0 0 1 10 5Zm0 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z\" clip-rule=\"evenodd\" /></svg>";
14
+ readonly warning: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z\" clip-rule=\"evenodd\" /></svg>";
15
+ readonly caution: "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0ZM8.28 7.22a.75.75 0 0 0-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 1 0 1.06 1.06L10 11.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L11.06 10l1.72-1.72a.75.75 0 0 0-1.06-1.06L10 8.94 8.28 7.22Z\" clip-rule=\"evenodd\" /></svg>";
16
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Core markdown parsing and transformation logic
3
+ */
4
+ import { Marked } from "marked";
5
+ import type { CDNConfig } from "../../types";
6
+ /**
7
+ * Set domains that should not have nofollow attribute
8
+ * @param exceptions - Array of domain names (e.g., ["example.com", "trusted-site.org"])
9
+ */
10
+ export declare function setNoFollowExceptions(exceptions: string[]): void;
11
+ /**
12
+ * Creates an isolated Marked instance with custom configuration.
13
+ * V17 best practice: Use instance-scoped configuration to avoid global mutations.
14
+ * @param cdnConfig - Optional CDN configuration for image URL transformation
15
+ * @returns Configured Marked instance
16
+ */
17
+ export declare function createMarked(cdnConfig?: CDNConfig): Marked;
18
+ /**
19
+ * Convert markdown to sanitized HTML
20
+ * @param markdownContent - Raw markdown string
21
+ * @param cdnConfig - Optional CDN configuration
22
+ * @returns Sanitized HTML string
23
+ */
24
+ export declare function convertMarkdownToHtml(markdownContent: string, cdnConfig?: CDNConfig): string;
25
+ /**
26
+ * Extract excerpt from markdown content
27
+ * Removes headings, code blocks, and formatting to create plain text excerpt
28
+ * @param content - Raw markdown content
29
+ * @param maxLength - Maximum length of excerpt (default: 200)
30
+ * @returns Plain text excerpt
31
+ */
32
+ export declare function extractExcerpt(content: string, maxLength?: number): string;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Frontmatter and business location validators
3
+ */
4
+ export interface ValidationError {
5
+ file: string;
6
+ type: "yaml" | "missing_field" | "file_not_found" | "unknown" | "validation";
7
+ message: string;
8
+ suggestion?: string;
9
+ }
10
+ /**
11
+ * Validate business location format
12
+ * Required fields: type, name, address, lat/lng (NOT latitude/longitude)
13
+ * @param business - Business location data (can be array or single object)
14
+ * @param filePath - File path for error reporting
15
+ * @returns ValidationError if invalid, null if valid
16
+ */
17
+ export declare function validateBusinessLocation(business: any, filePath: string): ValidationError | null;
18
+ /**
19
+ * Validate that tags don't contain spaces (must use hyphens)
20
+ * @param tags - Array of tag strings
21
+ * @param filePath - File path for error reporting
22
+ * @returns ValidationError if invalid, null if valid
23
+ */
24
+ export declare function validateTags(tags: string[], filePath: string): ValidationError | null;
25
+ /**
26
+ * Check for deprecated 'location' field (should use 'business' instead)
27
+ * @param data - Frontmatter data
28
+ * @param filePath - File path for error reporting
29
+ * @returns ValidationError if found, null otherwise
30
+ */
31
+ export declare function checkDeprecatedLocationField(data: any, filePath: string): ValidationError | null;
@@ -1,15 +1,20 @@
1
- import { Post, CDNConfig } from "../types";
2
- export declare function setNoFollowExceptions(exceptions: string[]): void;
3
- export declare function extractExcerpt(content: string, maxLength?: number): string;
4
- export declare function convertMarkdownToHtml(markdownContent: string, cdnConfig?: CDNConfig): string;
5
- export interface ParseError {
6
- file: string;
7
- type: "yaml" | "missing_field" | "file_not_found" | "unknown" | "validation";
8
- message: string;
9
- suggestion?: string;
10
- }
1
+ /**
2
+ * Markdown utilities - Main export file
3
+ * Re-exports from modular components for backward compatibility
4
+ */
5
+ import type { Post, CDNConfig } from "../types";
6
+ import { convertMarkdownToHtml, extractExcerpt, setNoFollowExceptions } from "./markdown/parser";
7
+ import type { ValidationError } from "./markdown/validators";
8
+ export { setNoFollowExceptions, extractExcerpt, convertMarkdownToHtml };
9
+ export type { ValidationError as ParseError };
11
10
  export interface ParseMarkdownResult {
12
11
  post: Post | null;
13
- error: ParseError | null;
12
+ error: ValidationError | null;
14
13
  }
14
+ /**
15
+ * Parse a markdown file with frontmatter validation
16
+ * @param filePath - Path to markdown file
17
+ * @param cdnConfig - Optional CDN configuration
18
+ * @returns ParseMarkdownResult with post data or error
19
+ */
15
20
  export declare function parseMarkdownFile(filePath: string, cdnConfig?: CDNConfig): Promise<ParseMarkdownResult>;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Pagination utilities
3
+ */
4
+ export interface PaginationData {
5
+ currentPage: number;
6
+ totalPages: number;
7
+ hasNextPage: boolean;
8
+ hasPrevPage: boolean;
9
+ nextPage: number | null;
10
+ prevPage: number | null;
11
+ pageSize: number;
12
+ totalItems: number;
13
+ pagePath: string;
14
+ }
15
+ /**
16
+ * Create pagination data for a list of items
17
+ * @param items - Array of items to paginate
18
+ * @param currentPage - Current page number (1-indexed)
19
+ * @param pageSize - Number of items per page
20
+ * @param pagePath - Base path for pagination (e.g., "/", "/tags/tech/")
21
+ * @returns Pagination data object
22
+ */
23
+ export declare function createPagination<T>(items: T[], currentPage: number, pageSize: number, pagePath: string): PaginationData;
24
+ /**
25
+ * Get paginated slice of items for a specific page
26
+ * @param items - Array of items to paginate
27
+ * @param page - Page number (1-indexed)
28
+ * @param pageSize - Number of items per page
29
+ * @returns Slice of items for the requested page
30
+ */
31
+ export declare function getPaginatedItems<T>(items: T[], page: number, pageSize: number): T[];
32
+ /**
33
+ * Calculate total number of pages needed for items
34
+ * @param totalItems - Total number of items
35
+ * @param pageSize - Number of items per page
36
+ * @returns Total number of pages
37
+ */
38
+ export declare function getTotalPages(totalItems: number, pageSize: number): number;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * XML building utilities for RSS and Sitemap generation
3
+ */
4
+ /**
5
+ * Escape special characters in XML text to prevent CDATA issues
6
+ * @param text - Text to escape
7
+ * @returns Escaped XML text
8
+ */
9
+ export declare function escapeXml(text: string): string;
10
+ /**
11
+ * Build XML sitemap URL entry
12
+ * @param loc - URL location
13
+ * @param lastmod - Last modification date (ISO string)
14
+ * @param changefreq - Change frequency
15
+ * @param priority - Priority (0.0 to 1.0)
16
+ * @returns XML string for sitemap URL entry
17
+ */
18
+ export declare function buildSitemapUrl(loc: string, lastmod: string, changefreq: string, priority: number): string;
19
+ /**
20
+ * Calculate priority based on content freshness
21
+ * @param date - Content date (ISO string)
22
+ * @param basePriority - Base priority value
23
+ * @param now - Current time (default: Date.now())
24
+ * @returns Adjusted priority value (0.0 to 1.0)
25
+ */
26
+ export declare function calculateFreshnessPriority(date: string, basePriority: number, now?: number): number;
27
+ /**
28
+ * Build RSS item with all metadata
29
+ * @param params - RSS item parameters
30
+ * @returns RSS item XML string
31
+ */
32
+ export interface RSSItemParams {
33
+ title: string;
34
+ link: string;
35
+ pubDate: string;
36
+ description: string;
37
+ content: string;
38
+ tags?: string[];
39
+ author?: string;
40
+ image?: string | null;
41
+ }
42
+ export declare function buildRSSItem(params: RSSItemParams): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunki",
3
- "version": "0.16.2",
3
+ "version": "0.17.0",
4
4
  "description": "An opinionated static site generator built with Bun featuring PostCSS integration and modern web development workflows",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",