@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.
@@ -0,0 +1,181 @@
1
+ import { basename } from 'path';
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 function buildNavigation(pages) {
21
+ // Group pages by their collection (directory)
22
+ const collections = new Map();
23
+ for (const page of pages) {
24
+ const collectionPath = getCollectionPath(page.url);
25
+ if (!collections.has(collectionPath)) {
26
+ collections.set(collectionPath, []);
27
+ }
28
+ collections.get(collectionPath).push(page);
29
+ }
30
+ const navNodes = [];
31
+ // Process root-level pages
32
+ if (collections.has('/')) {
33
+ const rootPages = collections.get('/').filter((page) => {
34
+ // Don't include pages that are index pages for other collections
35
+ const potentialCollectionPath = page.url;
36
+ return !collections.has(potentialCollectionPath) || page.url === '/';
37
+ });
38
+ for (const page of rootPages) {
39
+ navNodes.push(createNavNodeFromPage(page));
40
+ }
41
+ }
42
+ // Process collection directories
43
+ for (const [collectionPath, collectionPages] of collections) {
44
+ if (collectionPath !== '/') {
45
+ // Find the index page for this collection (if it exists)
46
+ const indexPage = pages.find((p) => p.url === collectionPath);
47
+ // Create collection node
48
+ const collectionNode = createCollectionNode(collectionPath, collectionPages, indexPage);
49
+ navNodes.push(collectionNode);
50
+ }
51
+ }
52
+ return sortNavigationNodes(navNodes);
53
+ }
54
+ /**
55
+ * Determines which collection a page belongs to based on its URL
56
+ */
57
+ function getCollectionPath(url) {
58
+ const pathParts = url.split('/').filter(Boolean);
59
+ if (pathParts.length <= 1) {
60
+ // Root level: / or /about
61
+ return '/';
62
+ }
63
+ else {
64
+ // Collection level: /blog/post-1 -> belongs to /blog
65
+ return '/' + pathParts.slice(0, -1).join('/');
66
+ }
67
+ }
68
+ /**
69
+ * Sorts pages within a single collection.
70
+ * Primary sort: order field (ascending)
71
+ * Secondary sort: publishedAt date (descending, newest first)
72
+ * Tertiary sort: title (ascending)
73
+ *
74
+ * @param pages - Pages to sort
75
+ * @returns Sorted array of pages
76
+ */
77
+ function sortPagesInCollection(pages) {
78
+ return pages.sort((a, b) => {
79
+ // Primary sort by order field
80
+ const orderA = typeof a.frontMatter.order === 'number' ? a.frontMatter.order : Number.MAX_SAFE_INTEGER;
81
+ const orderB = typeof b.frontMatter.order === 'number' ? b.frontMatter.order : Number.MAX_SAFE_INTEGER;
82
+ if (orderA !== orderB) {
83
+ return orderA - orderB;
84
+ }
85
+ // Secondary sort by publishedAt (newest first)
86
+ if (a.publishedAt && b.publishedAt) {
87
+ return b.publishedAt.getTime() - a.publishedAt.getTime();
88
+ }
89
+ if (a.publishedAt && !b.publishedAt)
90
+ return -1;
91
+ if (!a.publishedAt && b.publishedAt)
92
+ return 1;
93
+ // Tertiary sort by title
94
+ const titleA = a.frontMatter.title || basename(a.url);
95
+ const titleB = b.frontMatter.title || basename(b.url);
96
+ return titleA.localeCompare(titleB);
97
+ });
98
+ }
99
+ /**
100
+ * Sorts navigation nodes by order, then by title
101
+ */
102
+ function sortNavigationNodes(nodes) {
103
+ return nodes.sort((a, b) => {
104
+ // Primary sort by order
105
+ const orderA = typeof a.order === 'number' ? a.order : Number.MAX_SAFE_INTEGER;
106
+ const orderB = typeof b.order === 'number' ? b.order : Number.MAX_SAFE_INTEGER;
107
+ if (orderA !== orderB) {
108
+ return orderA - orderB;
109
+ }
110
+ // Secondary sort by title
111
+ return a.title.localeCompare(b.title);
112
+ });
113
+ }
114
+ /**
115
+ * Creates a navigation node from a single page.
116
+ *
117
+ * @param page - Page model to convert
118
+ * @returns Navigation node
119
+ */
120
+ function createNavNodeFromPage(page) {
121
+ const navNode = {
122
+ title: page.frontMatter.title || basename(page.url) || 'Untitled',
123
+ url: page.url,
124
+ path: page.url,
125
+ isCollection: false,
126
+ };
127
+ // Only add order if it's a number
128
+ if (typeof page.frontMatter.order === 'number') {
129
+ navNode.order = page.frontMatter.order;
130
+ }
131
+ // Only add publishedAt if it exists
132
+ if (page.publishedAt) {
133
+ navNode.publishedAt = page.publishedAt;
134
+ }
135
+ return navNode;
136
+ }
137
+ /**
138
+ * Creates a collection navigation node with child pages.
139
+ *
140
+ * @param collectionPath - Path of the collection
141
+ * @param pages - Pages in the collection
142
+ * @param indexPage - Optional index page for the collection
143
+ * @returns Navigation node representing the collection
144
+ */
145
+ function createCollectionNode(collectionPath, pages, indexPage) {
146
+ const collectionName = basename(collectionPath);
147
+ // Use index page data if available, otherwise derive from collection
148
+ const title = indexPage?.frontMatter.title || capitalizeFirst(collectionName);
149
+ const url = indexPage?.url || collectionPath;
150
+ const order = indexPage?.frontMatter.order;
151
+ const publishedAt = indexPage?.publishedAt;
152
+ const children = sortPagesInCollection(pages).map((page) => createNavNodeFromPage(page));
153
+ const navNode = {
154
+ title,
155
+ url,
156
+ path: collectionPath,
157
+ isCollection: true,
158
+ };
159
+ // Only add order if it's a number
160
+ if (typeof order === 'number') {
161
+ navNode.order = order;
162
+ }
163
+ // Only add publishedAt if it exists
164
+ if (publishedAt) {
165
+ navNode.publishedAt = publishedAt;
166
+ }
167
+ // Only add children if there are any
168
+ if (children.length > 0) {
169
+ navNode.children = children;
170
+ }
171
+ return navNode;
172
+ }
173
+ /**
174
+ * Capitalizes the first letter of a string.
175
+ *
176
+ * @param str - String to capitalize
177
+ * @returns Capitalized string
178
+ */
179
+ function capitalizeFirst(str) {
180
+ return str.charAt(0).toUpperCase() + str.slice(1);
181
+ }
@@ -0,0 +1,5 @@
1
+ import { Eta } from 'eta';
2
+ import type { StatiConfig, PageModel, NavNode } from '../types.js';
3
+ export declare function createTemplateEngine(config: StatiConfig): Eta;
4
+ export declare function renderPage(page: PageModel, body: string, config: StatiConfig, eta: Eta, navigation?: NavNode[], allPages?: PageModel[]): Promise<string>;
5
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/core/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAK1B,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAkB,MAAM,aAAa,CAAC;AAgRnF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,GAAG,CAS7D;AAED,wBAAsB,UAAU,CAC9B,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EACnB,GAAG,EAAE,GAAG,EACR,UAAU,CAAC,EAAE,OAAO,EAAE,EACtB,QAAQ,CAAC,EAAE,SAAS,EAAE,GACrB,OAAO,CAAC,MAAM,CAAC,CAgDjB"}
@@ -0,0 +1,307 @@
1
+ import { Eta } from 'eta';
2
+ import { join, dirname, relative, basename } from 'path';
3
+ import fse from 'fs-extra';
4
+ const { pathExists } = fse;
5
+ import glob from 'fast-glob';
6
+ /**
7
+ * Determines if the given page is an index page for a collection.
8
+ * An index page is one whose URL matches a directory path that contains other pages.
9
+ *
10
+ * @param page - The page to check
11
+ * @param allPages - All pages in the site
12
+ * @returns True if the page is a collection index page
13
+ */
14
+ function isCollectionIndexPage(page, allPages) {
15
+ // Root index page is always a collection index
16
+ if (page.url === '/') {
17
+ return true;
18
+ }
19
+ // Check if this page's URL is a directory path that contains other pages
20
+ const pageUrlAsDir = page.url.endsWith('/') ? page.url : page.url + '/';
21
+ return allPages.some((otherPage) => otherPage.url !== page.url && otherPage.url.startsWith(pageUrlAsDir));
22
+ }
23
+ /**
24
+ * Gets the collection path for a given page URL.
25
+ * For index pages, returns the page's URL. For child pages, returns the parent directory.
26
+ *
27
+ * @param pageUrl - The page URL
28
+ * @returns The collection path
29
+ */
30
+ function getCollectionPathForPage(pageUrl) {
31
+ if (pageUrl === '/') {
32
+ return '/';
33
+ }
34
+ const pathParts = pageUrl.split('/').filter(Boolean);
35
+ if (pathParts.length <= 1) {
36
+ return '/';
37
+ }
38
+ return '/' + pathParts.slice(0, -1).join('/');
39
+ }
40
+ /**
41
+ * Groups pages by their tags for aggregation purposes.
42
+ *
43
+ * @param pages - Pages to group
44
+ * @returns Object mapping tag names to arrays of pages
45
+ */
46
+ function groupPagesByTags(pages) {
47
+ const pagesByTag = {};
48
+ for (const page of pages) {
49
+ const tags = page.frontMatter.tags;
50
+ if (Array.isArray(tags)) {
51
+ for (const tag of tags) {
52
+ if (typeof tag === 'string') {
53
+ if (!pagesByTag[tag]) {
54
+ pagesByTag[tag] = [];
55
+ }
56
+ pagesByTag[tag].push(page);
57
+ }
58
+ }
59
+ }
60
+ }
61
+ return pagesByTag;
62
+ }
63
+ /**
64
+ * Sorts pages by publishedAt date (most recent first), falling back to alphabetical by title.
65
+ *
66
+ * @param pages - Pages to sort
67
+ * @returns Sorted array of pages
68
+ */
69
+ function sortPagesByDate(pages) {
70
+ return pages.sort((a, b) => {
71
+ // Sort by publishedAt (newest first)
72
+ if (a.publishedAt && b.publishedAt) {
73
+ return b.publishedAt.getTime() - a.publishedAt.getTime();
74
+ }
75
+ if (a.publishedAt && !b.publishedAt)
76
+ return -1;
77
+ if (!a.publishedAt && b.publishedAt)
78
+ return 1;
79
+ // Fallback to title
80
+ const titleA = a.frontMatter.title || basename(a.url) || 'Untitled';
81
+ const titleB = b.frontMatter.title || basename(b.url) || 'Untitled';
82
+ return titleA.localeCompare(titleB);
83
+ });
84
+ }
85
+ /**
86
+ * Builds collection aggregation data for index pages.
87
+ * Provides all the data needed for index pages to list and organize their collection's content.
88
+ *
89
+ * @param currentPage - The current page being rendered (must be an index page)
90
+ * @param allPages - All pages in the site
91
+ * @returns Collection data for template rendering
92
+ */
93
+ function buildCollectionData(currentPage, allPages) {
94
+ const collectionPath = currentPage.url === '/' ? '/' : currentPage.url;
95
+ const collectionName = collectionPath === '/' ? 'Home' : basename(collectionPath);
96
+ // Find all pages that belong to this collection
97
+ const collectionPages = allPages.filter((page) => {
98
+ if (page.url === currentPage.url) {
99
+ return false; // Don't include the index page itself
100
+ }
101
+ if (collectionPath === '/') {
102
+ // For root collection, include pages that are direct children of root
103
+ const pageCollectionPath = getCollectionPathForPage(page.url);
104
+ return pageCollectionPath === '/';
105
+ }
106
+ else {
107
+ // For other collections, include pages that start with the collection path
108
+ const pageUrlAsDir = collectionPath.endsWith('/') ? collectionPath : collectionPath + '/';
109
+ return page.url.startsWith(pageUrlAsDir);
110
+ }
111
+ });
112
+ // Find direct children (one level down)
113
+ const children = collectionPages.filter((page) => {
114
+ const relativePath = page.url.substring(collectionPath.length);
115
+ const cleanPath = relativePath.startsWith('/') ? relativePath.substring(1) : relativePath;
116
+ return !cleanPath.includes('/'); // No further slashes means direct child
117
+ });
118
+ // Sort pages by date for recent posts
119
+ const recentPages = sortPagesByDate([...collectionPages]);
120
+ // Group by tags
121
+ const pagesByTag = groupPagesByTags(collectionPages);
122
+ return {
123
+ pages: collectionPages,
124
+ children,
125
+ recentPages,
126
+ pagesByTag,
127
+ metadata: {
128
+ totalPages: collectionPages.length,
129
+ hasChildren: children.length > 0,
130
+ collectionPath,
131
+ collectionName,
132
+ },
133
+ };
134
+ }
135
+ /**
136
+ * Discovers partials in the hierarchy for a given page path.
137
+ * Scans all parent directories for folders starting with underscore.
138
+ *
139
+ * @param pagePath - The path to the page (relative to srcDir)
140
+ * @param config - Stati configuration
141
+ * @returns Object mapping partial names to their file paths
142
+ */
143
+ async function discoverPartials(pagePath, config) {
144
+ const srcDir = join(process.cwd(), config.srcDir);
145
+ const partials = {};
146
+ // Get the directory of the current page
147
+ const pageDir = dirname(pagePath);
148
+ const pathSegments = pageDir === '.' ? [] : pageDir.split('/');
149
+ // Scan from root to current directory
150
+ const dirsToScan = [''];
151
+ for (let i = 0; i < pathSegments.length; i++) {
152
+ dirsToScan.push(pathSegments.slice(0, i + 1).join('/'));
153
+ }
154
+ for (const dir of dirsToScan) {
155
+ const searchDir = dir ? join(srcDir, dir) : srcDir;
156
+ // Find all underscore folders in this directory level
157
+ const underscoreFolders = await glob('_*/', {
158
+ cwd: searchDir,
159
+ onlyDirectories: true,
160
+ });
161
+ // Scan each underscore folder for .eta files
162
+ for (const folder of underscoreFolders) {
163
+ const folderPath = join(searchDir, folder);
164
+ const etaFiles = await glob('**/*.eta', {
165
+ cwd: folderPath,
166
+ absolute: false,
167
+ });
168
+ for (const etaFile of etaFiles) {
169
+ const partialName = basename(etaFile, '.eta');
170
+ const fullPath = join(folderPath, etaFile);
171
+ const relativePath = relative(srcDir, fullPath);
172
+ // Store the relative path from srcDir for Eta to find it
173
+ partials[partialName] = relativePath;
174
+ }
175
+ }
176
+ }
177
+ return partials;
178
+ }
179
+ /**
180
+ * Discovers the appropriate layout file for a given page path.
181
+ * Implements the hierarchical layout.eta convention by searching
182
+ * from the page's directory up to the root.
183
+ *
184
+ * @param pagePath - The path to the page (relative to srcDir)
185
+ * @param config - Stati configuration
186
+ * @param explicitLayout - Layout specified in front matter (takes precedence)
187
+ * @param isIndexPage - Whether this is an aggregation/index page (enables index.eta lookup)
188
+ * @returns The layout file path or null if none found
189
+ */
190
+ async function discoverLayout(pagePath, config, explicitLayout, isIndexPage) {
191
+ const srcDir = join(process.cwd(), config.srcDir);
192
+ // If explicit layout is specified, use it
193
+ if (explicitLayout) {
194
+ const layoutPath = join(srcDir, `${explicitLayout}.eta`);
195
+ if (await pathExists(layoutPath)) {
196
+ return `${explicitLayout}.eta`;
197
+ }
198
+ }
199
+ // Get the directory of the current page
200
+ const pageDir = dirname(pagePath);
201
+ const pathSegments = pageDir === '.' ? [] : pageDir.split(/[/\\]/); // Handle both separators
202
+ // Search for layout.eta from current directory up to root
203
+ const dirsToSearch = [];
204
+ // Add current directory if not root
205
+ if (pathSegments.length > 0) {
206
+ for (let i = pathSegments.length; i > 0; i--) {
207
+ dirsToSearch.push(pathSegments.slice(0, i).join('/'));
208
+ }
209
+ }
210
+ // Add root directory
211
+ dirsToSearch.push('');
212
+ for (const dir of dirsToSearch) {
213
+ // For index pages, first check for index.eta in each directory
214
+ if (isIndexPage) {
215
+ const indexLayoutPath = dir ? join(srcDir, dir, 'index.eta') : join(srcDir, 'index.eta');
216
+ if (await pathExists(indexLayoutPath)) {
217
+ // Return relative path with forward slashes for Eta
218
+ const relativePath = dir ? `${dir}/index.eta` : 'index.eta';
219
+ return relativePath.replace(/\\/g, '/'); // Normalize to forward slashes
220
+ }
221
+ }
222
+ // Then check for layout.eta as fallback
223
+ const layoutPath = dir ? join(srcDir, dir, 'layout.eta') : join(srcDir, 'layout.eta');
224
+ if (await pathExists(layoutPath)) {
225
+ // Return relative path with forward slashes for Eta
226
+ const relativePath = dir ? `${dir}/layout.eta` : 'layout.eta';
227
+ return relativePath.replace(/\\/g, '/'); // Normalize to forward slashes
228
+ }
229
+ }
230
+ return null;
231
+ }
232
+ export function createTemplateEngine(config) {
233
+ const templateDir = join(process.cwd(), config.srcDir);
234
+ const eta = new Eta({
235
+ views: templateDir,
236
+ cache: process.env.NODE_ENV === 'production',
237
+ });
238
+ return eta;
239
+ }
240
+ export async function renderPage(page, body, config, eta, navigation, allPages) {
241
+ // Discover partials for this page's directory hierarchy
242
+ const srcDir = join(process.cwd(), config.srcDir);
243
+ const relativePath = relative(srcDir, page.sourcePath);
244
+ const partials = await discoverPartials(relativePath, config);
245
+ // Build collection data if this is an index page and all pages are provided
246
+ let collectionData;
247
+ const isIndexPage = allPages && isCollectionIndexPage(page, allPages);
248
+ if (isIndexPage) {
249
+ collectionData = buildCollectionData(page, allPages);
250
+ }
251
+ // Discover the appropriate layout using hierarchical layout.eta convention
252
+ // Pass isIndexPage flag to enable index.eta lookup for aggregation pages
253
+ const layoutPath = await discoverLayout(relativePath, config, page.frontMatter.layout, isIndexPage);
254
+ const context = {
255
+ site: config.site,
256
+ page: {
257
+ ...page.frontMatter,
258
+ path: page.url,
259
+ content: body,
260
+ },
261
+ content: body,
262
+ navigation: navigation || [],
263
+ partials, // Add discovered partials to template context
264
+ collection: collectionData, // Add collection data for index pages
265
+ // Add custom filters to context
266
+ ...(config.eta?.filters || {}),
267
+ };
268
+ try {
269
+ if (!layoutPath) {
270
+ console.warn('No layout template found, using fallback');
271
+ return createFallbackHtml(page, body);
272
+ }
273
+ return await eta.renderAsync(layoutPath, context);
274
+ }
275
+ catch (error) {
276
+ console.error(`Error rendering layout ${layoutPath || 'unknown'}:`, error);
277
+ return createFallbackHtml(page, body);
278
+ }
279
+ }
280
+ function createFallbackHtml(page, body) {
281
+ const title = page.frontMatter.title || 'Untitled';
282
+ const description = page.frontMatter.description || '';
283
+ return `<!DOCTYPE html>
284
+ <html lang="en">
285
+ <head>
286
+ <meta charset="UTF-8">
287
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
288
+ <title>${escapeHtml(title)}</title>
289
+ ${description ? `<meta name="description" content="${escapeHtml(description)}">` : ''}
290
+ </head>
291
+ <body>
292
+ <main>
293
+ ${body}
294
+ </main>
295
+ </body>
296
+ </html>`;
297
+ }
298
+ function escapeHtml(text) {
299
+ const map = {
300
+ '&': '&amp;',
301
+ '<': '&lt;',
302
+ '>': '&gt;',
303
+ '"': '&quot;',
304
+ "'": '&#39;',
305
+ };
306
+ return text.replace(/[&<>"']/g, (m) => map[m]);
307
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @fileoverview @stati/core - Core engine for Stati static site generator
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * import { build, loadConfig, defineConfig } from '@stati/core';
7
+ *
8
+ * // Define configuration with TypeScript support
9
+ * export default defineConfig({
10
+ * site: {
11
+ * title: 'My Site',
12
+ * baseUrl: 'https://example.com',
13
+ * },
14
+ * // ... other config options
15
+ * });
16
+ *
17
+ * // Load configuration and build site
18
+ * const config = await loadConfig();
19
+ * await build({ clean: true });
20
+ * ```
21
+ */
22
+ export type { StatiConfig, PageModel, FrontMatter, BuildContext, PageContext, BuildHooks, NavNode, ISGConfig, AgingRule, BuildStats, } from './types.js';
23
+ export type { BuildOptions } from './core/build.js';
24
+ export { build } from './core/build.js';
25
+ export { loadConfig } from './config/loader.js';
26
+ export { invalidate } from './core/invalidate.js';
27
+ import type { StatiConfig } from './types.js';
28
+ /**
29
+ * Helper function for defining Stati configuration with TypeScript IntelliSense.
30
+ * Provides type checking and autocompletion for configuration options.
31
+ *
32
+ * @param config - The Stati configuration object
33
+ * @returns The same configuration object with proper typing
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * import { defineConfig } from '@stati/core';
38
+ *
39
+ * export default defineConfig({
40
+ * site: {
41
+ * title: 'My Blog',
42
+ * baseUrl: 'https://myblog.com',
43
+ * },
44
+ * srcDir: 'content',
45
+ * outDir: 'public',
46
+ * isg: {
47
+ * enabled: true,
48
+ * ttlSeconds: 3600,
49
+ * },
50
+ * hooks: {
51
+ * beforeAll: async (ctx) => {
52
+ * console.log(`Building ${ctx.pages.length} pages`);
53
+ * },
54
+ * },
55
+ * });
56
+ * ```
57
+ */
58
+ export declare function defineConfig(config: StatiConfig): StatiConfig;
59
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,YAAY,EACV,WAAW,EACX,SAAS,EACT,WAAW,EACX,YAAY,EACZ,WAAW,EACX,UAAU,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,UAAU,GACX,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGlD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAE7D"}
package/dist/index.js ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @fileoverview @stati/core - Core engine for Stati static site generator
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * import { build, loadConfig, defineConfig } from '@stati/core';
7
+ *
8
+ * // Define configuration with TypeScript support
9
+ * export default defineConfig({
10
+ * site: {
11
+ * title: 'My Site',
12
+ * baseUrl: 'https://example.com',
13
+ * },
14
+ * // ... other config options
15
+ * });
16
+ *
17
+ * // Load configuration and build site
18
+ * const config = await loadConfig();
19
+ * await build({ clean: true });
20
+ * ```
21
+ */
22
+ export { build } from './core/build.js';
23
+ export { loadConfig } from './config/loader.js';
24
+ export { invalidate } from './core/invalidate.js';
25
+ /**
26
+ * Helper function for defining Stati configuration with TypeScript IntelliSense.
27
+ * Provides type checking and autocompletion for configuration options.
28
+ *
29
+ * @param config - The Stati configuration object
30
+ * @returns The same configuration object with proper typing
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * import { defineConfig } from '@stati/core';
35
+ *
36
+ * export default defineConfig({
37
+ * site: {
38
+ * title: 'My Blog',
39
+ * baseUrl: 'https://myblog.com',
40
+ * },
41
+ * srcDir: 'content',
42
+ * outDir: 'public',
43
+ * isg: {
44
+ * enabled: true,
45
+ * ttlSeconds: 3600,
46
+ * },
47
+ * hooks: {
48
+ * beforeAll: async (ctx) => {
49
+ * console.log(`Building ${ctx.pages.length} pages`);
50
+ * },
51
+ * },
52
+ * });
53
+ * ```
54
+ */
55
+ export function defineConfig(config) {
56
+ return config;
57
+ }