@stati/core 1.3.1 → 1.4.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 +7 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +17 -12
- package/dist/constants.d.ts +71 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +78 -0
- package/dist/core/build.d.ts +1 -1
- package/dist/core/build.d.ts.map +1 -1
- package/dist/core/build.js +94 -69
- package/dist/core/content.d.ts +1 -1
- package/dist/core/content.d.ts.map +1 -1
- package/dist/core/content.js +10 -5
- package/dist/core/dev.d.ts +1 -7
- package/dist/core/dev.d.ts.map +1 -1
- package/dist/core/dev.js +202 -141
- package/dist/core/invalidate.d.ts +1 -1
- package/dist/core/invalidate.d.ts.map +1 -1
- package/dist/core/invalidate.js +3 -3
- package/dist/core/isg/build-lock.d.ts.map +1 -1
- package/dist/core/isg/build-lock.js +4 -2
- package/dist/core/isg/builder.d.ts +1 -1
- package/dist/core/isg/builder.d.ts.map +1 -1
- package/dist/core/isg/deps.d.ts +1 -1
- package/dist/core/isg/deps.d.ts.map +1 -1
- package/dist/core/isg/deps.js +59 -78
- package/dist/core/isg/hash.d.ts.map +1 -1
- package/dist/core/isg/hash.js +26 -17
- package/dist/core/isg/manifest.d.ts +1 -1
- package/dist/core/isg/manifest.d.ts.map +1 -1
- package/dist/core/isg/manifest.js +21 -8
- package/dist/core/isg/ttl.d.ts +1 -1
- package/dist/core/isg/ttl.d.ts.map +1 -1
- package/dist/core/isg/ttl.js +6 -9
- package/dist/core/isg/validation.d.ts +1 -1
- package/dist/core/isg/validation.d.ts.map +1 -1
- package/dist/core/markdown.d.ts +1 -1
- package/dist/core/markdown.d.ts.map +1 -1
- package/dist/core/navigation.d.ts +1 -1
- package/dist/core/navigation.d.ts.map +1 -1
- package/dist/core/preview.d.ts +19 -0
- package/dist/core/preview.d.ts.map +1 -0
- package/dist/core/preview.js +163 -0
- package/dist/core/templates.d.ts +1 -1
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +28 -105
- package/dist/core/utils/fs.d.ts +37 -0
- package/dist/core/utils/fs.d.ts.map +1 -0
- package/dist/core/utils/fs.js +86 -0
- package/dist/core/utils/partials.d.ts +24 -0
- package/dist/core/utils/partials.d.ts.map +1 -0
- package/dist/core/utils/partials.js +85 -0
- package/dist/core/utils/paths.d.ts +67 -0
- package/dist/core/utils/paths.d.ts.map +1 -0
- package/dist/core/utils/paths.js +86 -0
- package/dist/core/utils/template-discovery.d.ts +34 -0
- package/dist/core/utils/template-discovery.d.ts.map +1 -0
- package/dist/core/utils/template-discovery.js +111 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/tests/utils/test-mocks.d.ts +69 -0
- package/dist/tests/utils/test-mocks.d.ts.map +1 -0
- package/dist/tests/utils/test-mocks.js +125 -0
- package/dist/types/config.d.ts +178 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +1 -0
- package/dist/types/content.d.ts +124 -0
- package/dist/types/content.d.ts.map +1 -0
- package/dist/types/content.js +4 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/isg.d.ts +103 -0
- package/dist/types/isg.d.ts.map +1 -0
- package/dist/types/isg.js +4 -0
- package/dist/types/logging.d.ts +113 -0
- package/dist/types/logging.d.ts.map +1 -0
- package/dist/types/logging.js +4 -0
- package/dist/types/navigation.d.ts +43 -0
- package/dist/types/navigation.d.ts.map +1 -0
- package/dist/types/navigation.js +4 -0
- package/dist/types.d.ts +10 -10
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/core/isg/deps.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { join, dirname, relative, posix } from 'path';
|
|
2
|
-
import
|
|
3
|
-
const { pathExists, readFile } = fse;
|
|
2
|
+
import { pathExists, readFile } from '../utils/fs.js';
|
|
4
3
|
import glob from 'fast-glob';
|
|
4
|
+
import { TEMPLATE_EXTENSION } from '../../constants.js';
|
|
5
|
+
import { isCollectionIndexPage, discoverLayout } from '../utils/template-discovery.js';
|
|
6
|
+
import { resolveSrcDir } from '../utils/paths.js';
|
|
5
7
|
/**
|
|
6
8
|
* Error thrown when a circular dependency is detected in templates.
|
|
7
9
|
*/
|
|
@@ -42,7 +44,7 @@ export async function trackTemplateDependencies(page, config) {
|
|
|
42
44
|
return [];
|
|
43
45
|
}
|
|
44
46
|
const deps = [];
|
|
45
|
-
const srcDir =
|
|
47
|
+
const srcDir = resolveSrcDir(config);
|
|
46
48
|
const relativePath = relative(srcDir, page.sourcePath);
|
|
47
49
|
// Track dependencies with circular detection
|
|
48
50
|
const visited = new Set();
|
|
@@ -80,7 +82,7 @@ export async function findPartialDependencies(pagePath, config) {
|
|
|
80
82
|
return [];
|
|
81
83
|
}
|
|
82
84
|
const deps = [];
|
|
83
|
-
const srcDir =
|
|
85
|
+
const srcDir = resolveSrcDir(config);
|
|
84
86
|
// Get the directory of the current page
|
|
85
87
|
const pageDir = dirname(pagePath);
|
|
86
88
|
const pathSegments = pageDir === '.' ? [] : pageDir.split(/[/\\]/);
|
|
@@ -135,71 +137,13 @@ export async function findPartialDependencies(pagePath, config) {
|
|
|
135
137
|
* ```
|
|
136
138
|
*/
|
|
137
139
|
export async function resolveTemplatePath(layout, config) {
|
|
138
|
-
const srcDir =
|
|
140
|
+
const srcDir = resolveSrcDir(config);
|
|
139
141
|
const layoutPath = join(srcDir, `${layout}.eta`);
|
|
140
142
|
if (await pathExists(layoutPath)) {
|
|
141
143
|
return layoutPath;
|
|
142
144
|
}
|
|
143
145
|
return null;
|
|
144
146
|
}
|
|
145
|
-
/**
|
|
146
|
-
* Helper function to determine if a page is a collection index page.
|
|
147
|
-
* Duplicated from templates.ts to avoid circular dependencies.
|
|
148
|
-
*/
|
|
149
|
-
function isCollectionIndexPage(page) {
|
|
150
|
-
// This is a simplified version - in a real implementation,
|
|
151
|
-
// we'd need access to all pages to determine this properly.
|
|
152
|
-
// For now, we'll assume any page ending in /index or at root is a collection page.
|
|
153
|
-
return page.url === '/' || page.url.endsWith('/index') || page.slug === 'index';
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Helper function to discover layout files.
|
|
157
|
-
* Duplicated from templates.ts to avoid circular dependencies.
|
|
158
|
-
*/
|
|
159
|
-
async function discoverLayout(pagePath, config, explicitLayout, isIndexPage) {
|
|
160
|
-
// Early return if required config values are missing
|
|
161
|
-
if (!config.srcDir) {
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
const srcDir = join(process.cwd(), config.srcDir);
|
|
165
|
-
// If explicit layout is specified, use it
|
|
166
|
-
if (explicitLayout) {
|
|
167
|
-
const layoutPath = join(srcDir, `${explicitLayout}.eta`);
|
|
168
|
-
if (await pathExists(layoutPath)) {
|
|
169
|
-
return `${explicitLayout}.eta`;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// Get the directory of the current page
|
|
173
|
-
const pageDir = dirname(pagePath);
|
|
174
|
-
const pathSegments = pageDir === '.' ? [] : pageDir.split(/[/\\]/);
|
|
175
|
-
// Search for layout.eta from current directory up to root
|
|
176
|
-
const dirsToSearch = [];
|
|
177
|
-
// Add current directory if not root
|
|
178
|
-
if (pathSegments.length > 0) {
|
|
179
|
-
for (let i = pathSegments.length; i > 0; i--) {
|
|
180
|
-
dirsToSearch.push(pathSegments.slice(0, i).join('/'));
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// Add root directory
|
|
184
|
-
dirsToSearch.push('');
|
|
185
|
-
for (const dir of dirsToSearch) {
|
|
186
|
-
// For index pages, first check for index.eta in each directory
|
|
187
|
-
if (isIndexPage) {
|
|
188
|
-
const indexLayoutPath = dir ? join(srcDir, dir, 'index.eta') : join(srcDir, 'index.eta');
|
|
189
|
-
if (await pathExists(indexLayoutPath)) {
|
|
190
|
-
const relativePath = dir ? `${dir}/index.eta` : 'index.eta';
|
|
191
|
-
return posix.normalize(relativePath);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// Then check for layout.eta as fallback
|
|
195
|
-
const layoutPath = dir ? join(srcDir, dir, 'layout.eta') : join(srcDir, 'layout.eta');
|
|
196
|
-
if (await pathExists(layoutPath)) {
|
|
197
|
-
const relativePath = dir ? `${dir}/layout.eta` : 'layout.eta';
|
|
198
|
-
return posix.normalize(relativePath);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
147
|
/**
|
|
204
148
|
* Detects circular dependencies in template includes/extends.
|
|
205
149
|
* Uses DFS to traverse the dependency graph and detect cycles.
|
|
@@ -233,6 +177,9 @@ async function detectCircularDependencies(templatePath, srcDir, visited, current
|
|
|
233
177
|
try {
|
|
234
178
|
// Read template content to find includes/extends
|
|
235
179
|
const content = await readFile(templatePath, 'utf-8');
|
|
180
|
+
if (!content) {
|
|
181
|
+
return; // Skip if file doesn't exist
|
|
182
|
+
}
|
|
236
183
|
const dependencies = await parseTemplateDependencies(content, templatePath, srcDir);
|
|
237
184
|
// Recursively check dependencies
|
|
238
185
|
for (const depPath of dependencies) {
|
|
@@ -264,6 +211,7 @@ async function detectCircularDependencies(templatePath, srcDir, visited, current
|
|
|
264
211
|
*/
|
|
265
212
|
async function parseTemplateDependencies(content, templatePath, srcDir) {
|
|
266
213
|
const dependencies = [];
|
|
214
|
+
const templateDir = dirname(templatePath);
|
|
267
215
|
// Look for Eta include patterns: <%~ include('template') %>
|
|
268
216
|
const includePatterns = [
|
|
269
217
|
/<%[~-]?\s*include\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
|
|
@@ -274,7 +222,7 @@ async function parseTemplateDependencies(content, templatePath, srcDir) {
|
|
|
274
222
|
while ((match = pattern.exec(content)) !== null) {
|
|
275
223
|
const includePath = match[1];
|
|
276
224
|
if (includePath) {
|
|
277
|
-
const resolvedPath = await resolveTemplatePathInternal(includePath, srcDir);
|
|
225
|
+
const resolvedPath = await resolveTemplatePathInternal(includePath, srcDir, templateDir);
|
|
278
226
|
if (resolvedPath) {
|
|
279
227
|
dependencies.push(resolvedPath);
|
|
280
228
|
}
|
|
@@ -291,7 +239,7 @@ async function parseTemplateDependencies(content, templatePath, srcDir) {
|
|
|
291
239
|
while ((match = pattern.exec(content)) !== null) {
|
|
292
240
|
const layoutPath = match[1];
|
|
293
241
|
if (layoutPath) {
|
|
294
|
-
const resolvedPath = await resolveTemplatePathInternal(layoutPath, srcDir);
|
|
242
|
+
const resolvedPath = await resolveTemplatePathInternal(layoutPath, srcDir, templateDir);
|
|
295
243
|
if (resolvedPath) {
|
|
296
244
|
dependencies.push(resolvedPath);
|
|
297
245
|
}
|
|
@@ -306,26 +254,59 @@ async function parseTemplateDependencies(content, templatePath, srcDir) {
|
|
|
306
254
|
*
|
|
307
255
|
* @param templateRef - Template reference from include/layout statement
|
|
308
256
|
* @param srcDir - Source directory for resolving paths
|
|
257
|
+
* @param currentDir - Current directory for hierarchical search (optional)
|
|
309
258
|
* @returns Absolute path to template file, or null if not found
|
|
310
259
|
*/
|
|
311
|
-
async function resolveTemplatePathInternal(templateRef, srcDir) {
|
|
312
|
-
|
|
313
|
-
|
|
260
|
+
async function resolveTemplatePathInternal(templateRef, srcDir, currentDir) {
|
|
261
|
+
const templateName = templateRef.endsWith(TEMPLATE_EXTENSION)
|
|
262
|
+
? templateRef
|
|
263
|
+
: `${templateRef}${TEMPLATE_EXTENSION}`;
|
|
314
264
|
// Try absolute path from srcDir
|
|
315
265
|
const absolutePath = join(srcDir, templateName);
|
|
316
266
|
if (await pathExists(absolutePath)) {
|
|
317
267
|
return absolutePath;
|
|
318
268
|
}
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
269
|
+
// Determine the starting directory for hierarchical search (relative to srcDir)
|
|
270
|
+
const startDir = currentDir ? relative(srcDir, currentDir) : '';
|
|
271
|
+
// Build list of directories to search (current dir up to srcDir root only)
|
|
272
|
+
// This ensures we never search outside the source directory boundary
|
|
273
|
+
const dirsToSearch = [];
|
|
274
|
+
// Safety check: ensure currentDir is within srcDir (relative path shouldn't start with '..')
|
|
275
|
+
if (startDir.startsWith('..')) {
|
|
276
|
+
// If currentDir is outside srcDir, only search at srcDir root
|
|
277
|
+
dirsToSearch.push('');
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
const pathSegments = startDir === '' ? [] : startDir.split(/[/\\]/);
|
|
281
|
+
// Add directories from current up to srcDir root (don't go beyond srcDir)
|
|
282
|
+
if (pathSegments.length > 0) {
|
|
283
|
+
for (let i = pathSegments.length; i >= 0; i--) {
|
|
284
|
+
if (i === 0) {
|
|
285
|
+
dirsToSearch.push(''); // srcDir root directory
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
dirsToSearch.push(pathSegments.slice(0, i).join('/'));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
dirsToSearch.push(''); // srcDir root directory only
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// Search each directory level for the template in underscore directories
|
|
297
|
+
for (const dir of dirsToSearch) {
|
|
298
|
+
const searchDir = dir ? join(srcDir, dir) : srcDir;
|
|
299
|
+
try {
|
|
300
|
+
// Search for template in underscore directories at this level only
|
|
301
|
+
const pattern = posix.join(searchDir.replace(/\\/g, '/'), '_*', templateName);
|
|
302
|
+
const matches = await glob(pattern, { absolute: true });
|
|
303
|
+
if (matches.length > 0) {
|
|
304
|
+
return matches[0]; // Return first match
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// Continue if directory doesn't exist or can't be read
|
|
309
|
+
continue;
|
|
329
310
|
}
|
|
330
311
|
}
|
|
331
312
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../../src/core/isg/hash.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../../src/core/isg/hash.ts"],"names":[],"mappings":"AAwBA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAKhG;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiB9E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAKnF"}
|
package/dist/core/isg/hash.js
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { createHash } from 'crypto';
|
|
2
|
-
import
|
|
3
|
-
|
|
2
|
+
import { readFile, pathExists } from '../utils/fs.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a SHA-256 hash instance, updates it with data, and returns the hex digest.
|
|
5
|
+
* Internal utility to eliminate duplicate hash creation patterns.
|
|
6
|
+
*
|
|
7
|
+
* @param data - Data to hash
|
|
8
|
+
* @returns SHA-256 hash as a hex string with 'sha256-' prefix
|
|
9
|
+
*/
|
|
10
|
+
function createSha256Hash(data) {
|
|
11
|
+
const hash = createHash('sha256');
|
|
12
|
+
if (Array.isArray(data)) {
|
|
13
|
+
for (const item of data) {
|
|
14
|
+
hash.update(item, 'utf-8');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
hash.update(data, 'utf-8');
|
|
19
|
+
}
|
|
20
|
+
return `sha256-${hash.digest('hex')}`;
|
|
21
|
+
}
|
|
4
22
|
/**
|
|
5
23
|
* Computes a SHA-256 hash of page content and front matter.
|
|
6
24
|
* Used to detect when page content has changed.
|
|
@@ -16,13 +34,9 @@ const { readFile, pathExists } = fse;
|
|
|
16
34
|
* ```
|
|
17
35
|
*/
|
|
18
36
|
export function computeContentHash(content, frontMatter) {
|
|
19
|
-
const hash = createHash('sha256');
|
|
20
|
-
// Hash the content
|
|
21
|
-
hash.update(content, 'utf-8');
|
|
22
37
|
// Hash the front matter (sorted for consistency)
|
|
23
38
|
const sortedFrontMatter = JSON.stringify(frontMatter, Object.keys(frontMatter).sort());
|
|
24
|
-
|
|
25
|
-
return `sha256-${hash.digest('hex')}`;
|
|
39
|
+
return createSha256Hash([content, sortedFrontMatter]);
|
|
26
40
|
}
|
|
27
41
|
/**
|
|
28
42
|
* Computes a SHA-256 hash of a file's contents.
|
|
@@ -45,9 +59,10 @@ export async function computeFileHash(filePath) {
|
|
|
45
59
|
return null;
|
|
46
60
|
}
|
|
47
61
|
const content = await readFile(filePath, 'utf-8');
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
62
|
+
if (!content) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return createSha256Hash(content);
|
|
51
66
|
}
|
|
52
67
|
catch (error) {
|
|
53
68
|
console.warn(`Failed to compute hash for ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -70,13 +85,7 @@ export async function computeFileHash(filePath) {
|
|
|
70
85
|
* ```
|
|
71
86
|
*/
|
|
72
87
|
export function computeInputsHash(contentHash, depsHashes) {
|
|
73
|
-
const hash = createHash('sha256');
|
|
74
|
-
// Hash the content hash
|
|
75
|
-
hash.update(contentHash, 'utf-8');
|
|
76
88
|
// Hash each dependency hash in sorted order for consistency
|
|
77
89
|
const sortedDepsHashes = [...depsHashes].sort();
|
|
78
|
-
|
|
79
|
-
hash.update(depHash, 'utf-8');
|
|
80
|
-
}
|
|
81
|
-
return `sha256-${hash.digest('hex')}`;
|
|
90
|
+
return createSha256Hash([contentHash, ...sortedDepsHashes]);
|
|
82
91
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/core/isg/manifest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/core/isg/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,sBAAsB,CAAC;AAUtE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CA8FvF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAmEhG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,IAAI,aAAa,CAInD"}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
const { readFile, writeFile, pathExists, ensureDir } = fse;
|
|
1
|
+
import { readFile, writeFile, pathExists, ensureDir } from '../utils/fs.js';
|
|
3
2
|
import { join } from 'path';
|
|
4
|
-
|
|
5
|
-
* Path to the cache manifest file within the cache directory
|
|
6
|
-
*/
|
|
7
|
-
const MANIFEST_FILENAME = 'manifest.json';
|
|
3
|
+
import { MANIFEST_FILENAME } from '../../constants.js';
|
|
8
4
|
/**
|
|
9
5
|
* Loads the ISG cache manifest from the cache directory.
|
|
10
6
|
* Returns null if no manifest exists or if it's corrupted.
|
|
@@ -28,6 +24,10 @@ export async function loadCacheManifest(cacheDir) {
|
|
|
28
24
|
return null;
|
|
29
25
|
}
|
|
30
26
|
const manifestContent = await readFile(manifestPath, 'utf-8');
|
|
27
|
+
if (!manifestContent) {
|
|
28
|
+
console.warn('Cache manifest not found or empty, creating new cache');
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
31
|
// Handle empty files
|
|
32
32
|
if (!manifestContent.trim()) {
|
|
33
33
|
console.warn('Cache manifest is empty, creating new cache');
|
|
@@ -119,11 +119,14 @@ export async function saveCacheManifest(cacheDir, manifest) {
|
|
|
119
119
|
}
|
|
120
120
|
catch (error) {
|
|
121
121
|
const nodeError = error;
|
|
122
|
-
|
|
122
|
+
// Handle specific error codes with custom messages
|
|
123
|
+
if (nodeError.code === 'EACCES' ||
|
|
124
|
+
(nodeError.message && nodeError.message.includes('Permission denied'))) {
|
|
123
125
|
throw new Error(`Permission denied saving cache manifest to ${manifestPath}. ` +
|
|
124
126
|
`Please check directory permissions or run with appropriate privileges.`);
|
|
125
127
|
}
|
|
126
|
-
if (nodeError.code === 'ENOSPC'
|
|
128
|
+
if (nodeError.code === 'ENOSPC' ||
|
|
129
|
+
(nodeError.message && nodeError.message.includes('No space left on device'))) {
|
|
127
130
|
throw new Error(`No space left on device when saving cache manifest to ${manifestPath}. ` +
|
|
128
131
|
`Please free up disk space and try again.`);
|
|
129
132
|
}
|
|
@@ -135,6 +138,16 @@ export async function saveCacheManifest(cacheDir, manifest) {
|
|
|
135
138
|
throw new Error(`Cache directory path ${cacheDir} is not a directory. ` +
|
|
136
139
|
`Please remove the conflicting file and try again.`);
|
|
137
140
|
}
|
|
141
|
+
// Re-throw the original error if it's already properly formatted
|
|
142
|
+
if (error instanceof Error) {
|
|
143
|
+
if (error.message.includes('Failed to create directory') ||
|
|
144
|
+
error.message.includes('Failed to write file')) {
|
|
145
|
+
throw new Error(`Failed to save cache manifest to ${manifestPath}: ${error.message.split(': ').slice(-1)[0]}`);
|
|
146
|
+
}
|
|
147
|
+
if (error.message.includes('Failed to')) {
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
138
151
|
throw new Error(`Failed to save cache manifest to ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
139
152
|
}
|
|
140
153
|
}
|
package/dist/core/isg/ttl.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PageModel, ISGConfig, AgingRule, CacheEntry } from '../../types.js';
|
|
1
|
+
import type { PageModel, ISGConfig, AgingRule, CacheEntry } from '../../types/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Safely gets the current UTC time with drift protection.
|
|
4
4
|
* Ensures all ISG operations use consistent UTC time.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ttl.d.ts","sourceRoot":"","sources":["../../../src/core/isg/ttl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"ttl.d.ts","sourceRoot":"","sources":["../../../src/core/isg/ttl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAQxF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,CAAC,EAAE,IAAI,GAAG,IAAI,CAW5D;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAqB5E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,SAAS,EACf,SAAS,EAAE,SAAS,EACpB,WAAW,CAAC,EAAE,IAAI,GACjB,MAAM,CAkBR;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE;IAC5C,GAAG,EAAE,IAAI,CAAC;IACV,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,IAAI,GAAG,IAAI,CAqBd;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAiBlE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,IAAI,EACjB,UAAU,EAAE,SAAS,EAAE,EACvB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,IAAI,GACR,MAAM,CAyBR"}
|
package/dist/core/isg/ttl.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Accounts for small differences between system clocks.
|
|
4
|
-
*/
|
|
5
|
-
const CLOCK_DRIFT_TOLERANCE_MS = 30000; // 30 seconds
|
|
1
|
+
import { CLOCK_DRIFT_TOLERANCE_MS, DEFAULT_TTL_SECONDS, MILLISECONDS_PER_DAY, } from '../../constants.js';
|
|
2
|
+
// dedup: time constants centralized in constants.ts
|
|
6
3
|
/**
|
|
7
4
|
* Safely gets the current UTC time with drift protection.
|
|
8
5
|
* Ensures all ISG operations use consistent UTC time.
|
|
@@ -73,11 +70,11 @@ export function computeEffectiveTTL(page, isgConfig, currentTime) {
|
|
|
73
70
|
const now = getSafeCurrentTime(currentTime);
|
|
74
71
|
// Apply aging rules if we have a published date and aging rules configured
|
|
75
72
|
if (publishedAt && isgConfig.aging && isgConfig.aging.length > 0) {
|
|
76
|
-
const defaultTTL = isgConfig.ttlSeconds ??
|
|
73
|
+
const defaultTTL = isgConfig.ttlSeconds ?? DEFAULT_TTL_SECONDS;
|
|
77
74
|
return applyAgingRules(publishedAt, isgConfig.aging, defaultTTL, now);
|
|
78
75
|
}
|
|
79
76
|
// Fall back to default TTL
|
|
80
|
-
return isgConfig.ttlSeconds ??
|
|
77
|
+
return isgConfig.ttlSeconds ?? DEFAULT_TTL_SECONDS;
|
|
81
78
|
}
|
|
82
79
|
/**
|
|
83
80
|
* Computes the next rebuild date for a page based on TTL and aging rules.
|
|
@@ -108,7 +105,7 @@ export function computeNextRebuildAt(options) {
|
|
|
108
105
|
// If there's a max age cap and published date, check if content is frozen
|
|
109
106
|
if (maxAgeCapDays && publishedAt) {
|
|
110
107
|
const normalizedPublishedAt = getSafeCurrentTime(publishedAt);
|
|
111
|
-
const maxAgeMs = maxAgeCapDays *
|
|
108
|
+
const maxAgeMs = maxAgeCapDays * MILLISECONDS_PER_DAY;
|
|
112
109
|
const ageMs = now.getTime() - normalizedPublishedAt.getTime();
|
|
113
110
|
if (ageMs > maxAgeMs) {
|
|
114
111
|
// Content is frozen, no rebuild needed
|
|
@@ -145,7 +142,7 @@ export function isPageFrozen(entry, now) {
|
|
|
145
142
|
// If we can't parse the published date, don't freeze
|
|
146
143
|
return false;
|
|
147
144
|
}
|
|
148
|
-
const maxAgeMs = entry.maxAgeCapDays *
|
|
145
|
+
const maxAgeMs = entry.maxAgeCapDays * MILLISECONDS_PER_DAY;
|
|
149
146
|
const ageMs = safeNow.getTime() - publishedAt.getTime();
|
|
150
147
|
return ageMs > maxAgeMs;
|
|
151
148
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/core/isg/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/core/isg/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,sBAAsB,CAAC;AAEjE;;;GAGG;AACH,oBAAY,kBAAkB;IAC5B,WAAW,oBAAoB;IAC/B,mBAAmB,4BAA4B;IAC/C,kBAAkB,2BAA2B;IAC7C,oBAAoB,6BAA6B;IACjD,oBAAoB,6BAA6B;IACjD,sBAAsB,+BAA+B;CACtD;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;aAE5B,IAAI,EAAE,kBAAkB;aACxB,KAAK,EAAE,MAAM;aACb,KAAK,EAAE,OAAO;gBAFd,IAAI,EAAE,kBAAkB,EACxB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,EAC9B,OAAO,EAAE,MAAM;CAKlB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAmBzD;AAmLD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,UAAU,EAAE,MAAM,GACjB,IAAI,CAwDN;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,MAAM,GAAG,SAAS,CAoCpB"}
|
package/dist/core/markdown.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import MarkdownIt from 'markdown-it';
|
|
2
|
-
import type { StatiConfig } from '../types.js';
|
|
2
|
+
import type { StatiConfig } from '../types/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* Creates and configures a MarkdownIt processor based on the provided configuration.
|
|
5
5
|
* Supports both plugin array format and configure function format.
|
|
@@ -1 +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,
|
|
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,mBAAmB,CAAC;AAErD;;;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"}
|
|
@@ -1 +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,
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/core/navigation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,OAAO,EAAE,CAyC7D"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Logger } from '../types/index.js';
|
|
2
|
+
export interface PreviewServerOptions {
|
|
3
|
+
port?: number;
|
|
4
|
+
host?: string;
|
|
5
|
+
open?: boolean;
|
|
6
|
+
configPath?: string;
|
|
7
|
+
logger?: Logger;
|
|
8
|
+
}
|
|
9
|
+
export interface PreviewServer {
|
|
10
|
+
start(): Promise<void>;
|
|
11
|
+
stop(): Promise<void>;
|
|
12
|
+
url: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates a preview server that serves the built site from the dist directory
|
|
16
|
+
* without live reload functionality, perfect for previewing the production build.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createPreviewServer(options?: PreviewServerOptions): Promise<PreviewServer>;
|
|
19
|
+
//# sourceMappingURL=preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/core/preview.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAKhD,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb;AAyBD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,aAAa,CAAC,CA+JxB"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import { join, extname } from 'path';
|
|
3
|
+
import { readFile, stat } from 'fs/promises';
|
|
4
|
+
import { loadConfig } from '../config/loader.js';
|
|
5
|
+
import { resolveDevPaths } from './utils/paths.js';
|
|
6
|
+
import { DEFAULT_DEV_PORT, DEFAULT_DEV_HOST } from '../constants.js';
|
|
7
|
+
/**
|
|
8
|
+
* Loads and validates configuration for the preview server.
|
|
9
|
+
*/
|
|
10
|
+
async function loadPreviewConfig(configPath, logger) {
|
|
11
|
+
try {
|
|
12
|
+
if (configPath) {
|
|
13
|
+
logger.info?.(`Loading config from: ${configPath}`);
|
|
14
|
+
}
|
|
15
|
+
const config = await loadConfig(process.cwd());
|
|
16
|
+
const { outDir } = resolveDevPaths(config);
|
|
17
|
+
return { outDir };
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
logger.error?.(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates a preview server that serves the built site from the dist directory
|
|
26
|
+
* without live reload functionality, perfect for previewing the production build.
|
|
27
|
+
*/
|
|
28
|
+
export async function createPreviewServer(options = {}) {
|
|
29
|
+
const { port = DEFAULT_DEV_PORT, host = DEFAULT_DEV_HOST, open = false, configPath, logger = {
|
|
30
|
+
info: () => { },
|
|
31
|
+
success: () => { },
|
|
32
|
+
error: (msg) => console.error(msg),
|
|
33
|
+
warning: (msg) => console.warn(msg),
|
|
34
|
+
building: () => { },
|
|
35
|
+
processing: () => { },
|
|
36
|
+
stats: () => { },
|
|
37
|
+
}, } = options;
|
|
38
|
+
const url = `http://${host}:${port}`;
|
|
39
|
+
let httpServer = null;
|
|
40
|
+
// Load configuration
|
|
41
|
+
const { outDir } = await loadPreviewConfig(configPath, logger);
|
|
42
|
+
/**
|
|
43
|
+
* Gets MIME type for a file based on its extension
|
|
44
|
+
*/
|
|
45
|
+
function getMimeType(filePath) {
|
|
46
|
+
const ext = extname(filePath).toLowerCase();
|
|
47
|
+
const mimeTypes = {
|
|
48
|
+
'.html': 'text/html',
|
|
49
|
+
'.js': 'application/javascript',
|
|
50
|
+
'.css': 'text/css',
|
|
51
|
+
'.json': 'application/json',
|
|
52
|
+
'.png': 'image/png',
|
|
53
|
+
'.jpg': 'image/jpeg',
|
|
54
|
+
'.jpeg': 'image/jpeg',
|
|
55
|
+
'.gif': 'image/gif',
|
|
56
|
+
'.svg': 'image/svg+xml',
|
|
57
|
+
'.ico': 'image/x-icon',
|
|
58
|
+
'.webp': 'image/webp',
|
|
59
|
+
'.woff': 'font/woff',
|
|
60
|
+
'.woff2': 'font/woff2',
|
|
61
|
+
'.ttf': 'font/ttf',
|
|
62
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
63
|
+
};
|
|
64
|
+
return mimeTypes[ext] || 'application/octet-stream';
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Serves files from the dist directory
|
|
68
|
+
*/
|
|
69
|
+
async function serveFile(requestPath) {
|
|
70
|
+
let filePath = join(outDir, requestPath === '/' ? 'index.html' : requestPath);
|
|
71
|
+
try {
|
|
72
|
+
const stats = await stat(filePath);
|
|
73
|
+
if (stats.isDirectory()) {
|
|
74
|
+
// Try to serve index.html from directory
|
|
75
|
+
const indexPath = join(filePath, 'index.html');
|
|
76
|
+
try {
|
|
77
|
+
await stat(indexPath);
|
|
78
|
+
filePath = indexPath;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return {
|
|
82
|
+
content: '404 - Directory listing not available',
|
|
83
|
+
mimeType: 'text/plain',
|
|
84
|
+
statusCode: 404,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const mimeType = getMimeType(filePath);
|
|
89
|
+
const content = await readFile(filePath);
|
|
90
|
+
// Unlike dev server, we don't inject live reload script in preview mode
|
|
91
|
+
return {
|
|
92
|
+
content,
|
|
93
|
+
mimeType,
|
|
94
|
+
statusCode: 200,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// File not found
|
|
99
|
+
return {
|
|
100
|
+
content: '404 - File not found',
|
|
101
|
+
mimeType: 'text/plain',
|
|
102
|
+
statusCode: 404,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const previewServer = {
|
|
107
|
+
url,
|
|
108
|
+
async start() {
|
|
109
|
+
// Create HTTP server
|
|
110
|
+
httpServer = createServer(async (req, res) => {
|
|
111
|
+
const requestPath = req.url || '/';
|
|
112
|
+
logger.processing?.(`${req.method} ${requestPath}`);
|
|
113
|
+
try {
|
|
114
|
+
const { content, mimeType, statusCode } = await serveFile(requestPath);
|
|
115
|
+
res.writeHead(statusCode, {
|
|
116
|
+
'Content-Type': mimeType,
|
|
117
|
+
'Access-Control-Allow-Origin': '*',
|
|
118
|
+
'Cache-Control': 'public, max-age=31536000', // Better caching for production preview
|
|
119
|
+
});
|
|
120
|
+
res.end(content);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
logger.error?.(`Server error: ${error instanceof Error ? error.message : String(error)}`);
|
|
124
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
125
|
+
res.end('500 - Internal Server Error');
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
// Start HTTP server
|
|
129
|
+
await new Promise((resolve, reject) => {
|
|
130
|
+
httpServer.listen(port, host, () => {
|
|
131
|
+
resolve();
|
|
132
|
+
});
|
|
133
|
+
httpServer.on('error', (error) => {
|
|
134
|
+
reject(error);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
logger.success?.(`Preview server running at ${url}`);
|
|
138
|
+
logger.info?.(`\nServing from:`);
|
|
139
|
+
logger.info?.(` 📁 ${outDir}`);
|
|
140
|
+
// Open browser if requested
|
|
141
|
+
if (open) {
|
|
142
|
+
try {
|
|
143
|
+
const { default: openBrowser } = await import('open');
|
|
144
|
+
await openBrowser(url);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
logger.info?.('Could not open browser automatically');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
async stop() {
|
|
152
|
+
if (httpServer) {
|
|
153
|
+
await new Promise((resolve) => {
|
|
154
|
+
httpServer.close(() => {
|
|
155
|
+
resolve();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
httpServer = null;
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
return previewServer;
|
|
163
|
+
}
|
package/dist/core/templates.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Eta } from 'eta';
|
|
2
|
-
import type { StatiConfig, PageModel, NavNode } from '../types.js';
|
|
2
|
+
import type { StatiConfig, PageModel, NavNode } from '../types/index.js';
|
|
3
3
|
export declare function createTemplateEngine(config: StatiConfig): Eta;
|
|
4
4
|
export declare function renderPage(page: PageModel, body: string, config: StatiConfig, eta: Eta, navigation?: NavNode[], allPages?: PageModel[]): Promise<string>;
|
|
5
5
|
//# sourceMappingURL=templates.d.ts.map
|