@stati/core 1.0.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/dist/config/loader.d.ts +23 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +73 -0
- package/dist/core/build.d.ts +51 -0
- package/dist/core/build.d.ts.map +1 -0
- package/dist/core/build.js +191 -0
- package/dist/core/content.d.ts +19 -0
- package/dist/core/content.d.ts.map +1 -0
- package/dist/core/content.js +68 -0
- package/dist/core/invalidate.d.ts +2 -0
- package/dist/core/invalidate.d.ts.map +1 -0
- package/dist/core/invalidate.js +7 -0
- package/dist/core/markdown.d.ts +9 -0
- package/dist/core/markdown.d.ts.map +1 -0
- package/dist/core/markdown.js +48 -0
- package/dist/core/navigation.d.ts +21 -0
- package/dist/core/navigation.d.ts.map +1 -0
- package/dist/core/navigation.js +181 -0
- package/dist/core/templates.d.ts +5 -0
- package/dist/core/templates.d.ts.map +1 -0
- package/dist/core/templates.js +307 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/types.d.ts +371 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { StatiConfig } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Loads and validates Stati configuration from the project directory.
|
|
4
|
+
* Searches for configuration files in order: stati.config.ts, stati.config.js, stati.config.mjs
|
|
5
|
+
*
|
|
6
|
+
* @param cwd - Current working directory to search for config files
|
|
7
|
+
* @returns Promise resolving to the merged configuration object
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { loadConfig } from 'stati';
|
|
12
|
+
*
|
|
13
|
+
* // Load config from current directory
|
|
14
|
+
* const config = await loadConfig();
|
|
15
|
+
*
|
|
16
|
+
* // Load config from specific directory
|
|
17
|
+
* const config = await loadConfig('/path/to/project');
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @throws {Error} When configuration file exists but contains invalid JavaScript/TypeScript
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadConfig(cwd?: string): Promise<StatiConfig>;
|
|
23
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAqB/C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,WAAW,CAAC,CAkClF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { join, resolve } from 'path';
|
|
3
|
+
import { pathToFileURL } from 'url';
|
|
4
|
+
/**
|
|
5
|
+
* Default configuration values for Stati.
|
|
6
|
+
* Used as fallback when no configuration file is found.
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_CONFIG = {
|
|
9
|
+
srcDir: 'site',
|
|
10
|
+
outDir: 'dist',
|
|
11
|
+
staticDir: 'public',
|
|
12
|
+
site: {
|
|
13
|
+
title: 'My Site',
|
|
14
|
+
baseUrl: 'http://localhost:3000',
|
|
15
|
+
},
|
|
16
|
+
isg: {
|
|
17
|
+
enabled: true,
|
|
18
|
+
ttlSeconds: 3600,
|
|
19
|
+
maxAgeCapDays: 365,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Loads and validates Stati configuration from the project directory.
|
|
24
|
+
* Searches for configuration files in order: stati.config.ts, stati.config.js, stati.config.mjs
|
|
25
|
+
*
|
|
26
|
+
* @param cwd - Current working directory to search for config files
|
|
27
|
+
* @returns Promise resolving to the merged configuration object
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* import { loadConfig } from 'stati';
|
|
32
|
+
*
|
|
33
|
+
* // Load config from current directory
|
|
34
|
+
* const config = await loadConfig();
|
|
35
|
+
*
|
|
36
|
+
* // Load config from specific directory
|
|
37
|
+
* const config = await loadConfig('/path/to/project');
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @throws {Error} When configuration file exists but contains invalid JavaScript/TypeScript
|
|
41
|
+
*/
|
|
42
|
+
export async function loadConfig(cwd = process.cwd()) {
|
|
43
|
+
const configPaths = [
|
|
44
|
+
join(cwd, 'stati.config.ts'),
|
|
45
|
+
join(cwd, 'stati.config.js'),
|
|
46
|
+
join(cwd, 'stati.config.mjs'),
|
|
47
|
+
];
|
|
48
|
+
let configPath = null;
|
|
49
|
+
for (const path of configPaths) {
|
|
50
|
+
if (existsSync(path)) {
|
|
51
|
+
configPath = path;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!configPath) {
|
|
56
|
+
return DEFAULT_CONFIG;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const configUrl = pathToFileURL(resolve(configPath)).href;
|
|
60
|
+
const module = await import(configUrl);
|
|
61
|
+
const userConfig = module.default || module;
|
|
62
|
+
return {
|
|
63
|
+
...DEFAULT_CONFIG,
|
|
64
|
+
...userConfig,
|
|
65
|
+
site: { ...DEFAULT_CONFIG.site, ...userConfig.site },
|
|
66
|
+
isg: { ...DEFAULT_CONFIG.isg, ...userConfig.isg },
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error('Error loading config:', error);
|
|
71
|
+
return DEFAULT_CONFIG;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { BuildStats } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Options for customizing the build process.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* const options: BuildOptions = {
|
|
8
|
+
* force: true, // Force rebuild of all pages
|
|
9
|
+
* clean: true, // Clean output directory before build
|
|
10
|
+
* configPath: './custom.config.js', // Custom config file path
|
|
11
|
+
* includeDrafts: true // Include draft pages in build
|
|
12
|
+
* };
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export interface BuildOptions {
|
|
16
|
+
/** Force rebuild of all pages, ignoring cache */
|
|
17
|
+
force?: boolean;
|
|
18
|
+
/** Clean the output directory before building */
|
|
19
|
+
clean?: boolean;
|
|
20
|
+
/** Path to a custom configuration file */
|
|
21
|
+
configPath?: string;
|
|
22
|
+
/** Include draft pages in the build */
|
|
23
|
+
includeDrafts?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Builds the static site by processing content files and generating HTML pages.
|
|
27
|
+
* This is the main entry point for Stati's build process.
|
|
28
|
+
*
|
|
29
|
+
* @param options - Build configuration options
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* import { build } from 'stati';
|
|
34
|
+
*
|
|
35
|
+
* // Basic build
|
|
36
|
+
* await build();
|
|
37
|
+
*
|
|
38
|
+
* // Build with options
|
|
39
|
+
* await build({
|
|
40
|
+
* clean: true,
|
|
41
|
+
* force: true,
|
|
42
|
+
* configPath: './custom.config.js'
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @throws {Error} When configuration loading fails
|
|
47
|
+
* @throws {Error} When content processing fails
|
|
48
|
+
* @throws {Error} When template rendering fails
|
|
49
|
+
*/
|
|
50
|
+
export declare function build(options?: BuildOptions): Promise<BuildStats>;
|
|
51
|
+
//# sourceMappingURL=build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/core/build.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAgB,UAAU,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAoFD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CAyG3E"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import fse from 'fs-extra';
|
|
2
|
+
const { ensureDir, writeFile, copy, remove, pathExists, stat, readdir } = fse;
|
|
3
|
+
import { join, dirname } from 'path';
|
|
4
|
+
import { loadConfig } from '../config/loader.js';
|
|
5
|
+
import { loadContent } from './content.js';
|
|
6
|
+
import { createMarkdownProcessor, renderMarkdown } from './markdown.js';
|
|
7
|
+
import { createTemplateEngine, renderPage } from './templates.js';
|
|
8
|
+
import { buildNavigation } from './navigation.js';
|
|
9
|
+
/**
|
|
10
|
+
* Recursively calculates the total size of a directory in bytes.
|
|
11
|
+
* Used for build statistics.
|
|
12
|
+
*
|
|
13
|
+
* @param dirPath - Path to the directory
|
|
14
|
+
* @returns Total size in bytes
|
|
15
|
+
*/
|
|
16
|
+
async function getDirectorySize(dirPath) {
|
|
17
|
+
if (!(await pathExists(dirPath))) {
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
let totalSize = 0;
|
|
21
|
+
const items = await readdir(dirPath, { withFileTypes: true });
|
|
22
|
+
for (const item of items) {
|
|
23
|
+
const itemPath = join(dirPath, item.name);
|
|
24
|
+
if (item.isDirectory()) {
|
|
25
|
+
totalSize += await getDirectorySize(itemPath);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const stats = await stat(itemPath);
|
|
29
|
+
totalSize += stats.size;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return totalSize;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Counts the number of files in a directory recursively.
|
|
36
|
+
* Used for build statistics.
|
|
37
|
+
*
|
|
38
|
+
* @param dirPath - Path to the directory
|
|
39
|
+
* @returns Total number of files
|
|
40
|
+
*/
|
|
41
|
+
async function countFilesInDirectory(dirPath) {
|
|
42
|
+
if (!(await pathExists(dirPath))) {
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
let fileCount = 0;
|
|
46
|
+
const items = await readdir(dirPath, { withFileTypes: true });
|
|
47
|
+
for (const item of items) {
|
|
48
|
+
const itemPath = join(dirPath, item.name);
|
|
49
|
+
if (item.isDirectory()) {
|
|
50
|
+
fileCount += await countFilesInDirectory(itemPath);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
fileCount++;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return fileCount;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Formats build statistics for display.
|
|
60
|
+
*
|
|
61
|
+
* @param stats - Build statistics to format
|
|
62
|
+
* @returns Formatted statistics string
|
|
63
|
+
*/
|
|
64
|
+
function formatBuildStats(stats) {
|
|
65
|
+
const sizeKB = (stats.outputSizeBytes / 1024).toFixed(1);
|
|
66
|
+
const timeSeconds = (stats.buildTimeMs / 1000).toFixed(2);
|
|
67
|
+
let output = `📊 Build Statistics:
|
|
68
|
+
⏱️ Build time: ${timeSeconds}s
|
|
69
|
+
📄 Pages built: ${stats.totalPages}
|
|
70
|
+
📦 Assets copied: ${stats.assetsCount}
|
|
71
|
+
💾 Output size: ${sizeKB} KB`;
|
|
72
|
+
if (stats.cacheHits !== undefined && stats.cacheMisses !== undefined) {
|
|
73
|
+
const totalCacheRequests = stats.cacheHits + stats.cacheMisses;
|
|
74
|
+
const hitRate = totalCacheRequests > 0 ? ((stats.cacheHits / totalCacheRequests) * 100).toFixed(1) : '0';
|
|
75
|
+
output += `
|
|
76
|
+
🎯 Cache hits: ${stats.cacheHits}/${totalCacheRequests} (${hitRate}%)`;
|
|
77
|
+
}
|
|
78
|
+
return output;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Builds the static site by processing content files and generating HTML pages.
|
|
82
|
+
* This is the main entry point for Stati's build process.
|
|
83
|
+
*
|
|
84
|
+
* @param options - Build configuration options
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* import { build } from 'stati';
|
|
89
|
+
*
|
|
90
|
+
* // Basic build
|
|
91
|
+
* await build();
|
|
92
|
+
*
|
|
93
|
+
* // Build with options
|
|
94
|
+
* await build({
|
|
95
|
+
* clean: true,
|
|
96
|
+
* force: true,
|
|
97
|
+
* configPath: './custom.config.js'
|
|
98
|
+
* });
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @throws {Error} When configuration loading fails
|
|
102
|
+
* @throws {Error} When content processing fails
|
|
103
|
+
* @throws {Error} When template rendering fails
|
|
104
|
+
*/
|
|
105
|
+
export async function build(options = {}) {
|
|
106
|
+
const buildStartTime = Date.now();
|
|
107
|
+
console.log('🏗️ Building site...');
|
|
108
|
+
// Load configuration
|
|
109
|
+
const config = await loadConfig(options.configPath ? dirname(options.configPath) : process.cwd());
|
|
110
|
+
const outDir = join(process.cwd(), config.outDir);
|
|
111
|
+
// Create .stati cache directory
|
|
112
|
+
const cacheDir = join(process.cwd(), '.stati');
|
|
113
|
+
await ensureDir(cacheDir);
|
|
114
|
+
// Clean output directory if requested
|
|
115
|
+
if (options.clean) {
|
|
116
|
+
console.log('🧹 Cleaning output directory...');
|
|
117
|
+
await remove(outDir);
|
|
118
|
+
}
|
|
119
|
+
await ensureDir(outDir);
|
|
120
|
+
// Load all content
|
|
121
|
+
const pages = await loadContent(config, options.includeDrafts);
|
|
122
|
+
console.log(`📄 Found ${pages.length} pages`);
|
|
123
|
+
// Build navigation from pages
|
|
124
|
+
const navigation = buildNavigation(pages);
|
|
125
|
+
console.log(`🧭 Built navigation with ${navigation.length} top-level items`);
|
|
126
|
+
// Create processors
|
|
127
|
+
const md = await createMarkdownProcessor(config);
|
|
128
|
+
const eta = createTemplateEngine(config);
|
|
129
|
+
// Build context
|
|
130
|
+
const buildContext = { config, pages };
|
|
131
|
+
// Run beforeAll hook
|
|
132
|
+
if (config.hooks?.beforeAll) {
|
|
133
|
+
await config.hooks.beforeAll(buildContext);
|
|
134
|
+
}
|
|
135
|
+
// Render each page
|
|
136
|
+
for (const page of pages) {
|
|
137
|
+
console.log(` Building ${page.url}`);
|
|
138
|
+
// Run beforeRender hook
|
|
139
|
+
if (config.hooks?.beforeRender) {
|
|
140
|
+
await config.hooks.beforeRender({ page, config });
|
|
141
|
+
}
|
|
142
|
+
// Render markdown to HTML
|
|
143
|
+
const htmlContent = renderMarkdown(page.content, md);
|
|
144
|
+
// Render with template
|
|
145
|
+
const finalHtml = await renderPage(page, htmlContent, config, eta, navigation, pages);
|
|
146
|
+
// Determine output path - fix the logic here
|
|
147
|
+
let outputPath;
|
|
148
|
+
if (page.url === '/') {
|
|
149
|
+
outputPath = join(outDir, 'index.html');
|
|
150
|
+
}
|
|
151
|
+
else if (page.url.endsWith('/')) {
|
|
152
|
+
outputPath = join(outDir, page.url, 'index.html');
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
outputPath = join(outDir, `${page.url}.html`);
|
|
156
|
+
}
|
|
157
|
+
// Ensure directory exists and write file
|
|
158
|
+
await ensureDir(dirname(outputPath));
|
|
159
|
+
await writeFile(outputPath, finalHtml, 'utf-8');
|
|
160
|
+
// Run afterRender hook
|
|
161
|
+
if (config.hooks?.afterRender) {
|
|
162
|
+
await config.hooks.afterRender({ page, config });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Copy static assets and count them
|
|
166
|
+
let assetsCount = 0;
|
|
167
|
+
const staticDir = join(process.cwd(), config.staticDir);
|
|
168
|
+
if (await pathExists(staticDir)) {
|
|
169
|
+
await copy(staticDir, outDir, { overwrite: true });
|
|
170
|
+
assetsCount = await countFilesInDirectory(staticDir);
|
|
171
|
+
console.log(`📦 Copied ${assetsCount} static assets`);
|
|
172
|
+
}
|
|
173
|
+
// Run afterAll hook
|
|
174
|
+
if (config.hooks?.afterAll) {
|
|
175
|
+
await config.hooks.afterAll(buildContext);
|
|
176
|
+
}
|
|
177
|
+
// Calculate build statistics
|
|
178
|
+
const buildEndTime = Date.now();
|
|
179
|
+
const buildStats = {
|
|
180
|
+
totalPages: pages.length,
|
|
181
|
+
assetsCount,
|
|
182
|
+
buildTimeMs: buildEndTime - buildStartTime,
|
|
183
|
+
outputSizeBytes: await getDirectorySize(outDir),
|
|
184
|
+
// Cache stats would be populated here when caching is implemented
|
|
185
|
+
cacheHits: 0,
|
|
186
|
+
cacheMisses: 0,
|
|
187
|
+
};
|
|
188
|
+
console.log('✅ Build complete!');
|
|
189
|
+
console.log(formatBuildStats(buildStats));
|
|
190
|
+
return buildStats;
|
|
191
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PageModel, StatiConfig } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Loads and parses all content files from the configured source directory.
|
|
4
|
+
*
|
|
5
|
+
* @param config - The STATI configuration object
|
|
6
|
+
* @param includeDrafts - Whether to include draft pages (marked with draft: true)
|
|
7
|
+
* @returns Array of parsed page models
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Load all content including drafts
|
|
12
|
+
* const pages = await loadContent(config, true);
|
|
13
|
+
*
|
|
14
|
+
* // Load only published content
|
|
15
|
+
* const publishedPages = await loadContent(config, false);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadContent(config: StatiConfig, includeDrafts?: boolean): Promise<PageModel[]>;
|
|
19
|
+
//# sourceMappingURL=content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/core/content.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1D;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,aAAa,CAAC,EAAE,OAAO,GACtB,OAAO,CAAC,SAAS,EAAE,CAAC,CAyCtB"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import glob from 'fast-glob';
|
|
2
|
+
import fse from 'fs-extra';
|
|
3
|
+
const { readFile } = fse;
|
|
4
|
+
import matter from 'gray-matter';
|
|
5
|
+
import { join, relative, dirname, basename } from 'path';
|
|
6
|
+
/**
|
|
7
|
+
* Loads and parses all content files from the configured source directory.
|
|
8
|
+
*
|
|
9
|
+
* @param config - The STATI configuration object
|
|
10
|
+
* @param includeDrafts - Whether to include draft pages (marked with draft: true)
|
|
11
|
+
* @returns Array of parsed page models
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Load all content including drafts
|
|
16
|
+
* const pages = await loadContent(config, true);
|
|
17
|
+
*
|
|
18
|
+
* // Load only published content
|
|
19
|
+
* const publishedPages = await loadContent(config, false);
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export async function loadContent(config, includeDrafts) {
|
|
23
|
+
const contentDir = join(process.cwd(), config.srcDir);
|
|
24
|
+
// Exclude folders starting with underscore from content discovery
|
|
25
|
+
const files = await glob('**/*.md', {
|
|
26
|
+
cwd: contentDir,
|
|
27
|
+
absolute: true,
|
|
28
|
+
ignore: ['**/_*/**', '_*/**'],
|
|
29
|
+
});
|
|
30
|
+
const pages = [];
|
|
31
|
+
for (const file of files) {
|
|
32
|
+
const content = await readFile(file, 'utf-8');
|
|
33
|
+
const { data: frontMatter, content: markdown } = matter(content);
|
|
34
|
+
// Skip drafts unless explicitly included
|
|
35
|
+
if (frontMatter.draft && !includeDrafts) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const relativePath = relative(contentDir, file);
|
|
39
|
+
const slug = computeSlug(relativePath);
|
|
40
|
+
const url = computeUrl(slug, frontMatter);
|
|
41
|
+
const page = {
|
|
42
|
+
slug,
|
|
43
|
+
url,
|
|
44
|
+
sourcePath: file,
|
|
45
|
+
frontMatter,
|
|
46
|
+
content: markdown,
|
|
47
|
+
};
|
|
48
|
+
if (frontMatter.publishedAt && typeof frontMatter.publishedAt === 'string') {
|
|
49
|
+
page.publishedAt = new Date(frontMatter.publishedAt);
|
|
50
|
+
}
|
|
51
|
+
pages.push(page);
|
|
52
|
+
}
|
|
53
|
+
return pages;
|
|
54
|
+
}
|
|
55
|
+
function computeSlug(relativePath) {
|
|
56
|
+
const dir = dirname(relativePath);
|
|
57
|
+
const name = basename(relativePath, '.md');
|
|
58
|
+
if (name === 'index') {
|
|
59
|
+
return dir === '.' ? '/' : `/${dir}`;
|
|
60
|
+
}
|
|
61
|
+
return dir === '.' ? `/${name}` : `/${dir}/${name}`;
|
|
62
|
+
}
|
|
63
|
+
function computeUrl(slug, frontMatter) {
|
|
64
|
+
if (frontMatter.permalink && typeof frontMatter.permalink === 'string') {
|
|
65
|
+
return frontMatter.permalink;
|
|
66
|
+
}
|
|
67
|
+
return slug.replace(/\/index$/, '/');
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invalidate.d.ts","sourceRoot":"","sources":["../../src/core/invalidate.ts"],"names":[],"mappings":"AAAA,wBAAsB,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM9D"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import MarkdownIt from 'markdown-it';
|
|
2
|
+
import type { StatiConfig } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates and configures a MarkdownIt processor based on the provided configuration.
|
|
5
|
+
* Supports both plugin array format and configure function format.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createMarkdownProcessor(config: StatiConfig): Promise<MarkdownIt>;
|
|
8
|
+
export declare function renderMarkdown(content: string, md: MarkdownIt): string;
|
|
9
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/core/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAuCtF;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,MAAM,CAEtE"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import MarkdownIt from 'markdown-it';
|
|
2
|
+
/**
|
|
3
|
+
* Creates and configures a MarkdownIt processor based on the provided configuration.
|
|
4
|
+
* Supports both plugin array format and configure function format.
|
|
5
|
+
*/
|
|
6
|
+
export async function createMarkdownProcessor(config) {
|
|
7
|
+
const md = new MarkdownIt({
|
|
8
|
+
html: true,
|
|
9
|
+
linkify: true,
|
|
10
|
+
typographer: true,
|
|
11
|
+
});
|
|
12
|
+
// Apply plugins from array format
|
|
13
|
+
if (config.markdown?.plugins) {
|
|
14
|
+
for (const plugin of config.markdown.plugins) {
|
|
15
|
+
if (typeof plugin === 'string') {
|
|
16
|
+
// Plugin name only
|
|
17
|
+
try {
|
|
18
|
+
const pluginModule = await import(`markdown-it-${plugin}`);
|
|
19
|
+
const pluginFunction = pluginModule.default || pluginModule;
|
|
20
|
+
md.use(pluginFunction);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.warn(`Failed to load markdown plugin: markdown-it-${plugin}`, error);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else if (Array.isArray(plugin) && plugin.length >= 1) {
|
|
27
|
+
// Plugin name with options [name, options]
|
|
28
|
+
const [pluginName, options] = plugin;
|
|
29
|
+
try {
|
|
30
|
+
const pluginModule = await import(`markdown-it-${pluginName}`);
|
|
31
|
+
const pluginFunction = pluginModule.default || pluginModule;
|
|
32
|
+
md.use(pluginFunction, options);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.warn(`Failed to load markdown plugin: markdown-it-${pluginName}`, error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Apply user configuration function (this runs after plugins for override capability)
|
|
41
|
+
if (config.markdown?.configure) {
|
|
42
|
+
config.markdown.configure(md);
|
|
43
|
+
}
|
|
44
|
+
return md;
|
|
45
|
+
}
|
|
46
|
+
export function renderMarkdown(content, md) {
|
|
47
|
+
return md.render(content);
|
|
48
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PageModel, NavNode } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a hierarchical navigation structure from pages.
|
|
4
|
+
* Groups pages by directory structure and sorts them appropriately.
|
|
5
|
+
*
|
|
6
|
+
* @param pages - Array of page models to build navigation from
|
|
7
|
+
* @returns Array of top-level navigation nodes
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const pages = [
|
|
12
|
+
* { url: '/blog/post-1', frontMatter: { title: 'Post 1', order: 2 } },
|
|
13
|
+
* { url: '/blog/post-2', frontMatter: { title: 'Post 2', order: 1 } },
|
|
14
|
+
* { url: '/about', frontMatter: { title: 'About' } }
|
|
15
|
+
* ];
|
|
16
|
+
* const nav = buildNavigation(pages);
|
|
17
|
+
* // Results in hierarchical structure with sorted blog posts
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildNavigation(pages: PageModel[]): NavNode[];
|
|
21
|
+
//# sourceMappingURL=navigation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/core/navigation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,OAAO,EAAE,CAyC7D"}
|