@stati/core 1.3.0 → 1.3.2
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 +135 -126
- 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/templates.d.ts +1 -1
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +25 -103
- 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 +2 -2
- package/dist/index.d.ts.map +1 -1
- 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/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"}
|
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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/core/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/core/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG1B,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAkB,MAAM,mBAAmB,CAAC;AAiLzF,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,CAkEjB"}
|
package/dist/core/templates.js
CHANGED
|
@@ -1,43 +1,9 @@
|
|
|
1
1
|
import { Eta } from 'eta';
|
|
2
2
|
import { join, dirname, relative, basename } from 'path';
|
|
3
|
-
import { posix } from 'path';
|
|
4
|
-
import fse from 'fs-extra';
|
|
5
|
-
const { pathExists } = fse;
|
|
6
3
|
import glob from 'fast-glob';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
* @param page - The page to check
|
|
12
|
-
* @param allPages - All pages in the site
|
|
13
|
-
* @returns True if the page is a collection index page
|
|
14
|
-
*/
|
|
15
|
-
function isCollectionIndexPage(page, allPages) {
|
|
16
|
-
// Root index page is always a collection index
|
|
17
|
-
if (page.url === '/') {
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
// Check if this page's URL is a directory path that contains other pages
|
|
21
|
-
const pageUrlAsDir = page.url.endsWith('/') ? page.url : page.url + '/';
|
|
22
|
-
return allPages.some((otherPage) => otherPage.url !== page.url && otherPage.url.startsWith(pageUrlAsDir));
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Gets the collection path for a given page URL.
|
|
26
|
-
* For index pages, returns the page's URL. For child pages, returns the parent directory.
|
|
27
|
-
*
|
|
28
|
-
* @param pageUrl - The page URL
|
|
29
|
-
* @returns The collection path
|
|
30
|
-
*/
|
|
31
|
-
function getCollectionPathForPage(pageUrl) {
|
|
32
|
-
if (pageUrl === '/') {
|
|
33
|
-
return '/';
|
|
34
|
-
}
|
|
35
|
-
const pathParts = pageUrl.split('/').filter(Boolean);
|
|
36
|
-
if (pathParts.length <= 1) {
|
|
37
|
-
return '/';
|
|
38
|
-
}
|
|
39
|
-
return '/' + pathParts.slice(0, -1).join('/');
|
|
40
|
-
}
|
|
4
|
+
import { TEMPLATE_EXTENSION } from '../constants.js';
|
|
5
|
+
import { isCollectionIndexPage, discoverLayout, getCollectionPathForPage, } from './utils/template-discovery.js';
|
|
6
|
+
import { resolveSrcDir } from './utils/paths.js';
|
|
41
7
|
/**
|
|
42
8
|
* Groups pages by their tags for aggregation purposes.
|
|
43
9
|
*
|
|
@@ -142,96 +108,52 @@ function buildCollectionData(currentPage, allPages) {
|
|
|
142
108
|
* @returns Object mapping partial names to their file paths
|
|
143
109
|
*/
|
|
144
110
|
async function discoverPartials(pagePath, config) {
|
|
145
|
-
const srcDir =
|
|
111
|
+
const srcDir = resolveSrcDir(config);
|
|
146
112
|
const partials = {};
|
|
147
113
|
// Get the directory of the current page
|
|
148
114
|
const pageDir = dirname(pagePath);
|
|
149
115
|
const pathSegments = pageDir === '.' ? [] : pageDir.split('/');
|
|
150
|
-
// Scan from root to current directory
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
116
|
+
// Scan from root to current directory (least specific first)
|
|
117
|
+
// This allows more specific partials to override less specific ones
|
|
118
|
+
const dirsToScan = [];
|
|
119
|
+
if (pathSegments.length > 0) {
|
|
120
|
+
// Add root directory first, then parent directories, then current
|
|
121
|
+
dirsToScan.push(''); // Root directory first
|
|
122
|
+
// Add directories from root to current
|
|
123
|
+
for (let i = 1; i <= pathSegments.length; i++) {
|
|
124
|
+
dirsToScan.push(pathSegments.slice(0, i).join('/'));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
dirsToScan.push(''); // Root directory only
|
|
154
129
|
}
|
|
155
130
|
for (const dir of dirsToScan) {
|
|
156
131
|
const searchDir = dir ? join(srcDir, dir) : srcDir;
|
|
157
132
|
// Find all underscore folders in this directory level
|
|
158
|
-
const
|
|
159
|
-
|
|
133
|
+
const globPattern = join(searchDir, '_*/').replace(/\\/g, '/');
|
|
134
|
+
const underscoreFolders = await glob(globPattern, {
|
|
135
|
+
absolute: true,
|
|
160
136
|
onlyDirectories: true,
|
|
161
137
|
});
|
|
162
138
|
// Scan each underscore folder for .eta files
|
|
163
|
-
for (const
|
|
164
|
-
const folderPath = join(searchDir, folder);
|
|
139
|
+
for (const folderPath of underscoreFolders) {
|
|
165
140
|
const etaFiles = await glob('**/*.eta', {
|
|
166
141
|
cwd: folderPath,
|
|
167
142
|
absolute: false,
|
|
168
143
|
});
|
|
169
144
|
for (const etaFile of etaFiles) {
|
|
170
|
-
const partialName = basename(etaFile,
|
|
145
|
+
const partialName = basename(etaFile, TEMPLATE_EXTENSION);
|
|
171
146
|
const fullPath = join(folderPath, etaFile);
|
|
147
|
+
// Get relative path from srcDir to the partial file
|
|
172
148
|
const relativePath = relative(srcDir, fullPath);
|
|
173
|
-
// Store the relative path from srcDir for Eta to find it
|
|
174
149
|
partials[partialName] = relativePath;
|
|
175
150
|
}
|
|
176
151
|
}
|
|
177
152
|
}
|
|
178
153
|
return partials;
|
|
179
154
|
}
|
|
180
|
-
/**
|
|
181
|
-
* Discovers the appropriate layout file for a given page path.
|
|
182
|
-
* Implements the hierarchical layout.eta convention by searching
|
|
183
|
-
* from the page's directory up to the root.
|
|
184
|
-
*
|
|
185
|
-
* @param pagePath - The path to the page (relative to srcDir)
|
|
186
|
-
* @param config - Stati configuration
|
|
187
|
-
* @param explicitLayout - Layout specified in front matter (takes precedence)
|
|
188
|
-
* @param isIndexPage - Whether this is an aggregation/index page (enables index.eta lookup)
|
|
189
|
-
* @returns The layout file path or null if none found
|
|
190
|
-
*/
|
|
191
|
-
async function discoverLayout(pagePath, config, explicitLayout, isIndexPage) {
|
|
192
|
-
const srcDir = join(process.cwd(), config.srcDir);
|
|
193
|
-
// If explicit layout is specified, use it
|
|
194
|
-
if (explicitLayout) {
|
|
195
|
-
const layoutPath = join(srcDir, `${explicitLayout}.eta`);
|
|
196
|
-
if (await pathExists(layoutPath)) {
|
|
197
|
-
return `${explicitLayout}.eta`;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
// Get the directory of the current page
|
|
201
|
-
const pageDir = dirname(pagePath);
|
|
202
|
-
const pathSegments = pageDir === '.' ? [] : pageDir.split(/[/\\]/); // Handle both separators
|
|
203
|
-
// Search for layout.eta from current directory up to root
|
|
204
|
-
const dirsToSearch = [];
|
|
205
|
-
// Add current directory if not root
|
|
206
|
-
if (pathSegments.length > 0) {
|
|
207
|
-
for (let i = pathSegments.length; i > 0; i--) {
|
|
208
|
-
dirsToSearch.push(pathSegments.slice(0, i).join('/'));
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// Add root directory
|
|
212
|
-
dirsToSearch.push('');
|
|
213
|
-
for (const dir of dirsToSearch) {
|
|
214
|
-
// For index pages, first check for index.eta in each directory
|
|
215
|
-
if (isIndexPage) {
|
|
216
|
-
const indexLayoutPath = dir ? join(srcDir, dir, 'index.eta') : join(srcDir, 'index.eta');
|
|
217
|
-
if (await pathExists(indexLayoutPath)) {
|
|
218
|
-
// Return relative path with forward slashes for Eta
|
|
219
|
-
const relativePath = dir ? `${dir}/index.eta` : 'index.eta';
|
|
220
|
-
return posix.normalize(relativePath);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
// Then check for layout.eta as fallback
|
|
224
|
-
const layoutPath = dir ? join(srcDir, dir, 'layout.eta') : join(srcDir, 'layout.eta');
|
|
225
|
-
if (await pathExists(layoutPath)) {
|
|
226
|
-
// Return relative path with forward slashes for Eta
|
|
227
|
-
const relativePath = dir ? `${dir}/layout.eta` : 'layout.eta';
|
|
228
|
-
return posix.normalize(relativePath);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
return null;
|
|
232
|
-
}
|
|
233
155
|
export function createTemplateEngine(config) {
|
|
234
|
-
const templateDir =
|
|
156
|
+
const templateDir = resolveSrcDir(config);
|
|
235
157
|
const eta = new Eta({
|
|
236
158
|
views: templateDir,
|
|
237
159
|
cache: process.env.NODE_ENV === 'production',
|
|
@@ -240,7 +162,7 @@ export function createTemplateEngine(config) {
|
|
|
240
162
|
}
|
|
241
163
|
export async function renderPage(page, body, config, eta, navigation, allPages) {
|
|
242
164
|
// Discover partials for this page's directory hierarchy
|
|
243
|
-
const srcDir =
|
|
165
|
+
const srcDir = resolveSrcDir(config);
|
|
244
166
|
const relativePath = relative(srcDir, page.sourcePath);
|
|
245
167
|
const partialPaths = await discoverPartials(relativePath, config);
|
|
246
168
|
// Build collection data if this is an index page and all pages are provided
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fse from 'fs-extra';
|
|
2
|
+
import type { WriteFileOptions } from 'fs';
|
|
3
|
+
/**
|
|
4
|
+
* Safely reads a file with consistent error handling.
|
|
5
|
+
*/
|
|
6
|
+
export declare function readFile(filePath: string, encoding?: 'utf-8' | 'utf8'): Promise<string | null>;
|
|
7
|
+
/**
|
|
8
|
+
* Safely writes content to a file with consistent error handling.
|
|
9
|
+
*/
|
|
10
|
+
export declare function writeFile(filePath: string, content: string, options?: WriteFileOptions): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Checks if a path exists with consistent error handling.
|
|
13
|
+
*/
|
|
14
|
+
export declare function pathExists(filePath: string): Promise<boolean>;
|
|
15
|
+
/**
|
|
16
|
+
* Ensures a directory exists with consistent error handling.
|
|
17
|
+
*/
|
|
18
|
+
export declare function ensureDir(dirPath: string): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Removes a file or directory with consistent error handling.
|
|
21
|
+
*/
|
|
22
|
+
export declare function remove(path: string): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Gets file stats with consistent error handling.
|
|
25
|
+
*/
|
|
26
|
+
export declare function stat(filePath: string): Promise<fse.Stats>;
|
|
27
|
+
/**
|
|
28
|
+
* Reads directory contents with consistent error handling.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readdir<T extends boolean = false>(dirPath: string, options?: {
|
|
31
|
+
withFileTypes?: T;
|
|
32
|
+
}): Promise<T extends true ? fse.Dirent[] : string[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Copies a file with consistent error handling.
|
|
35
|
+
*/
|
|
36
|
+
export declare function copyFile(src: string, dest: string): Promise<void>;
|
|
37
|
+
//# sourceMappingURL=fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../../src/core/utils/fs.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,IAAI,CAAC;AAkD3C;;GAEG;AACH,wBAAsB,QAAQ,CAC5B,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,OAAO,GAAG,MAAgB,GACnC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAExB;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEnE;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9D;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAExD;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAE/D;AAED;;GAEG;AACH,wBAAsB,OAAO,CAAC,CAAC,SAAS,OAAO,GAAG,KAAK,EACrD,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,CAAC,CAAA;CAAE,GAC9B,OAAO,CAAC,CAAC,SAAS,IAAI,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAYnD;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvE"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fse from 'fs-extra';
|
|
2
|
+
const { readFile: fseReadFile, writeFile: fseWriteFile, pathExists: fsePathExists, ensureDir: fseEnsureDir, remove: fseRemove, stat: fseStat, readdir: fseReaddir, copyFile: fseCopyFile, } = fse;
|
|
3
|
+
/**
|
|
4
|
+
* Wraps fs-extra operations with consistent error handling.
|
|
5
|
+
*/
|
|
6
|
+
async function wrapFsOperation(operation, fsCall, path) {
|
|
7
|
+
try {
|
|
8
|
+
return await fsCall;
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
const nodeError = error;
|
|
12
|
+
throw new Error(`Failed to ${operation} ${path}: ${nodeError.message}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Wraps fs-extra operations that should return null on ENOENT.
|
|
17
|
+
*/
|
|
18
|
+
async function wrapFsOperationNullable(operation, fsCall, path) {
|
|
19
|
+
try {
|
|
20
|
+
return await fsCall;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
const nodeError = error;
|
|
24
|
+
if (nodeError.code === 'ENOENT') {
|
|
25
|
+
return null; // File doesn't exist
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`Failed to ${operation} ${path}: ${nodeError.message}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Safely reads a file with consistent error handling.
|
|
32
|
+
*/
|
|
33
|
+
export async function readFile(filePath, encoding = 'utf-8') {
|
|
34
|
+
return wrapFsOperationNullable('read file', fseReadFile(filePath, encoding), filePath);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Safely writes content to a file with consistent error handling.
|
|
38
|
+
*/
|
|
39
|
+
export async function writeFile(filePath, content, options) {
|
|
40
|
+
return wrapFsOperation('write file', fseWriteFile(filePath, content, options), filePath);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Checks if a path exists with consistent error handling.
|
|
44
|
+
*/
|
|
45
|
+
export async function pathExists(filePath) {
|
|
46
|
+
return wrapFsOperation('check path', fsePathExists(filePath), filePath);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Ensures a directory exists with consistent error handling.
|
|
50
|
+
*/
|
|
51
|
+
export async function ensureDir(dirPath) {
|
|
52
|
+
return wrapFsOperation('create directory', fseEnsureDir(dirPath), dirPath);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Removes a file or directory with consistent error handling.
|
|
56
|
+
*/
|
|
57
|
+
export async function remove(path) {
|
|
58
|
+
return wrapFsOperation('remove', fseRemove(path), path);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Gets file stats with consistent error handling.
|
|
62
|
+
*/
|
|
63
|
+
export async function stat(filePath) {
|
|
64
|
+
return wrapFsOperation('get stats for', fseStat(filePath), filePath);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Reads directory contents with consistent error handling.
|
|
68
|
+
*/
|
|
69
|
+
export async function readdir(dirPath, options) {
|
|
70
|
+
try {
|
|
71
|
+
if (options?.withFileTypes) {
|
|
72
|
+
return (await fseReaddir(dirPath, { withFileTypes: true }));
|
|
73
|
+
}
|
|
74
|
+
return (await fseReaddir(dirPath));
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
const nodeError = error;
|
|
78
|
+
throw new Error(`Failed to read directory ${dirPath}: ${nodeError.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Copies a file with consistent error handling.
|
|
83
|
+
*/
|
|
84
|
+
export async function copyFile(src, dest) {
|
|
85
|
+
return wrapFsOperation('copy', fseCopyFile(src, dest), `${src} to ${dest}`);
|
|
86
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { StatiConfig } from '../../types.js';
|
|
2
|
+
export interface PartialEntry {
|
|
3
|
+
name: string;
|
|
4
|
+
absPath: string;
|
|
5
|
+
relFromSrc: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Scan underscore folders from root to the page's directory for .eta partials.
|
|
9
|
+
* Returns structured entries usable by both renderer and ISG deps.
|
|
10
|
+
*/
|
|
11
|
+
export declare function scanPartialsHierarchy(pageRelativePath: string, config: StatiConfig): Promise<PartialEntry[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Build the list of directories to scan for partials from root → current page directory.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildDirsToScan(pageRelativePath: string): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Renderer-focused partial discovery.
|
|
18
|
+
* Matches historical renderer behavior used by templates tests:
|
|
19
|
+
* - Only scans the current page directory (not parents)
|
|
20
|
+
* - First finds underscore folders (e.g. "_partials/")
|
|
21
|
+
* - Then finds .eta files within those folders, returning relative paths like "_partials/header.eta"
|
|
22
|
+
*/
|
|
23
|
+
export declare function scanPartialsForRenderer(pageRelativePath: string, config: StatiConfig): Promise<PartialEntry[]>;
|
|
24
|
+
//# sourceMappingURL=partials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partials.d.ts","sourceRoot":"","sources":["../../../src/core/utils/partials.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,YAAY,EAAE,CAAC,CA2BzB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,EAAE,CASlE;AAED;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAC3C,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,YAAY,EAAE,CAAC,CAkCzB"}
|