@stati/core 1.8.0 → 1.10.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/README.md +6 -6
- package/dist/core/build.d.ts.map +1 -1
- package/dist/core/build.js +39 -4
- package/dist/core/dev.d.ts.map +1 -1
- package/dist/core/dev.js +70 -2
- package/dist/core/isg/deps.js +21 -0
- package/dist/core/isg/hash.d.ts +14 -0
- package/dist/core/isg/hash.d.ts.map +1 -1
- package/dist/core/isg/hash.js +32 -1
- package/dist/core/isg/index.d.ts +1 -1
- package/dist/core/isg/index.d.ts.map +1 -1
- package/dist/core/isg/index.js +1 -1
- package/dist/core/isg/manifest.d.ts.map +1 -1
- package/dist/core/isg/manifest.js +7 -1
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +43 -8
- package/dist/core/utils/callable-partials.d.ts +60 -0
- package/dist/core/utils/callable-partials.d.ts.map +1 -0
- package/dist/core/utils/callable-partials.js +108 -0
- package/dist/core/utils/index.d.ts +4 -0
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core/utils/index.js +6 -0
- package/dist/core/utils/navigation-helpers.d.ts +124 -0
- package/dist/core/utils/navigation-helpers.d.ts.map +1 -0
- package/dist/core/utils/navigation-helpers.js +219 -0
- package/dist/core/utils/partial-validation.d.ts +5 -2
- package/dist/core/utils/partial-validation.d.ts.map +1 -1
- package/dist/core/utils/partial-validation.js +35 -7
- package/dist/core/utils/server.d.ts +1 -1
- package/dist/core/utils/server.d.ts.map +1 -1
- package/dist/core/utils/server.js +14 -1
- package/dist/core/utils/tailwind-inventory.d.ts +91 -0
- package/dist/core/utils/tailwind-inventory.d.ts.map +1 -0
- package/dist/core/utils/tailwind-inventory.js +228 -0
- package/dist/core/utils/template-utils.d.ts +3 -0
- package/dist/core/utils/template-utils.d.ts.map +1 -1
- package/dist/core/utils/template-utils.js +27 -3
- package/dist/types/content.d.ts +24 -3
- package/dist/types/content.d.ts.map +1 -1
- package/dist/types/isg.d.ts +4 -1
- package/dist/types/isg.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -327,7 +327,7 @@ export default defineConfig({
|
|
|
327
327
|
});
|
|
328
328
|
```
|
|
329
329
|
|
|
330
|
-
> **For the complete configuration reference** including all options, advanced features, and detailed explanations, see the [Configuration Guide](https://docs.stati.build/configuration/).
|
|
330
|
+
> **For the complete configuration reference** including all options, advanced features, and detailed explanations, see the [Configuration Guide](https://docs.stati.build/configuration/file/).
|
|
331
331
|
|
|
332
332
|
---
|
|
333
333
|
|
|
@@ -621,14 +621,14 @@ Automatic navigation hierarchy based on your file structure:
|
|
|
621
621
|
```html
|
|
622
622
|
<!-- Show breadcrumbs -->
|
|
623
623
|
<nav>
|
|
624
|
-
<% stati.
|
|
624
|
+
<% stati.nav.getBreadcrumbs().forEach(crumb => { %>
|
|
625
625
|
<a href="<%= crumb.url %>"><%= crumb.title %></a>
|
|
626
626
|
<% }) %>
|
|
627
627
|
</nav>
|
|
628
628
|
|
|
629
629
|
<!-- Show navigation tree -->
|
|
630
630
|
<ul>
|
|
631
|
-
<% stati.
|
|
631
|
+
<% stati.nav.tree.forEach(item => { %>
|
|
632
632
|
<li><a href="<%= item.url %>"><%= item.title %></a></li>
|
|
633
633
|
<% }) %>
|
|
634
634
|
</ul>
|
|
@@ -739,9 +739,9 @@ export default defineConfig({
|
|
|
739
739
|
## Learn More
|
|
740
740
|
|
|
741
741
|
- [**Full Documentation**](https://docs.stati.build) — Complete guides and tutorials
|
|
742
|
-
- [**Configuration Guide**](https://docs.stati.build/configuration/) — All options explained
|
|
743
|
-
- [**API Reference**](https://docs.stati.build/api/) — Detailed API docs
|
|
744
|
-
- [**Examples**](https://docs.stati.build/examples/) — Real-world projects
|
|
742
|
+
- [**Configuration Guide**](https://docs.stati.build/configuration/file/) — All options explained
|
|
743
|
+
- [**API Reference**](https://docs.stati.build/api/reference/) — Detailed API docs
|
|
744
|
+
- [**Examples**](https://docs.stati.build/examples/list/) — Real-world projects
|
|
745
745
|
- [**Contributing**](https://github.com/ianchak/stati/blob/main/CONTRIBUTING.md) — Help improve Stati
|
|
746
746
|
|
|
747
747
|
---
|
package/dist/core/build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/core/build.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/core/build.ts"],"names":[],"mappings":"AAyCA,OAAO,KAAK,EAEV,UAAU,EACV,MAAM,EAKP,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA4FD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CAW3E"}
|
package/dist/core/build.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ensureDir, writeFile, remove, pathExists, stat, readdir, copyFile, resolveOutDir, resolveStaticDir, resolveCacheDir, } from './utils/index.js';
|
|
1
|
+
import { ensureDir, writeFile, remove, pathExists, stat, readdir, copyFile, resolveOutDir, resolveStaticDir, resolveCacheDir, enableInventoryTracking, disableInventoryTracking, clearInventory, writeTailwindClassInventory, getInventorySize, isTailwindUsed, loadPreviousInventory, } from './utils/index.js';
|
|
2
2
|
import { join, dirname, relative } from 'path';
|
|
3
3
|
import { posix } from 'path';
|
|
4
4
|
import { loadConfig } from '../config/loader.js';
|
|
@@ -6,7 +6,7 @@ import { loadContent } from './content.js';
|
|
|
6
6
|
import { createMarkdownProcessor, renderMarkdown } from './markdown.js';
|
|
7
7
|
import { createTemplateEngine, renderPage } from './templates.js';
|
|
8
8
|
import { buildNavigation } from './navigation.js';
|
|
9
|
-
import { loadCacheManifest, saveCacheManifest, shouldRebuildPage, createCacheEntry, updateCacheEntry, withBuildLock, } from './isg/index.js';
|
|
9
|
+
import { loadCacheManifest, saveCacheManifest, shouldRebuildPage, createCacheEntry, updateCacheEntry, withBuildLock, computeNavigationHash, } from './isg/index.js';
|
|
10
10
|
import { generateSitemap, generateRobotsTxtFromConfig, autoInjectSEO, } from '../seo/index.js';
|
|
11
11
|
/**
|
|
12
12
|
* Recursively calculates the total size of a directory in bytes.
|
|
@@ -172,10 +172,12 @@ async function loadContentAndBuildNavigation(config, options, logger) {
|
|
|
172
172
|
if (logger.navigationTree) {
|
|
173
173
|
logger.navigationTree(navigation);
|
|
174
174
|
}
|
|
175
|
+
// Compute navigation hash for change detection in dev server
|
|
176
|
+
const navigationHash = computeNavigationHash(navigation);
|
|
175
177
|
// Create processors
|
|
176
178
|
const md = await createMarkdownProcessor(config);
|
|
177
179
|
const eta = createTemplateEngine(config);
|
|
178
|
-
return { pages, navigation, md, eta };
|
|
180
|
+
return { pages, navigation, md, eta, navigationHash };
|
|
179
181
|
}
|
|
180
182
|
/**
|
|
181
183
|
* Processes pages with ISG caching logic.
|
|
@@ -362,17 +364,50 @@ async function buildInternal(options = {}) {
|
|
|
362
364
|
await remove(cacheDir);
|
|
363
365
|
}
|
|
364
366
|
await ensureDir(outDir);
|
|
367
|
+
// Enable Tailwind class inventory tracking only if Tailwind is detected
|
|
368
|
+
const hasTailwind = await isTailwindUsed();
|
|
369
|
+
if (hasTailwind) {
|
|
370
|
+
enableInventoryTracking();
|
|
371
|
+
clearInventory(); // Clear any previous inventory
|
|
372
|
+
// Try to load from existing inventory file
|
|
373
|
+
const loadedCount = await loadPreviousInventory(cacheDir);
|
|
374
|
+
if (loadedCount > 0) {
|
|
375
|
+
// Write the initial inventory file immediately so Tailwind can scan it
|
|
376
|
+
// This is critical for dev server where Tailwind starts watching before template rendering
|
|
377
|
+
await writeTailwindClassInventory(cacheDir);
|
|
378
|
+
logger.info(`📦 Loaded ${loadedCount} classes from previous build for Tailwind scanner`);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
// No previous inventory found - write an empty placeholder file
|
|
382
|
+
// This ensures Tailwind has a file to scan even on first build
|
|
383
|
+
// It will be populated with actual classes after template rendering
|
|
384
|
+
await writeTailwindClassInventory(cacheDir);
|
|
385
|
+
logger.info(`📦 Created inventory file for Tailwind scanner (will be populated after rendering)`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
365
388
|
// Load cache manifest for ISG (after potential clean operation)
|
|
366
389
|
const { manifest } = await setupCacheAndManifest(cacheDir);
|
|
367
390
|
// Load content and build navigation
|
|
368
391
|
console.log(); // Add spacing before content loading
|
|
369
|
-
const { pages, navigation, md, eta } = await loadContentAndBuildNavigation(config, options, logger);
|
|
392
|
+
const { pages, navigation, md, eta, navigationHash } = await loadContentAndBuildNavigation(config, options, logger);
|
|
393
|
+
// Store navigation hash in manifest for change detection in dev server
|
|
394
|
+
manifest.navigationHash = navigationHash;
|
|
370
395
|
// Process pages with ISG caching logic
|
|
371
396
|
console.log(); // Add spacing before page processing
|
|
372
397
|
const buildTime = new Date();
|
|
373
398
|
const pageProcessingResult = await processPagesWithCache(pages, manifest, config, outDir, md, eta, navigation, buildTime, options, logger);
|
|
374
399
|
cacheHits = pageProcessingResult.cacheHits;
|
|
375
400
|
cacheMisses = pageProcessingResult.cacheMisses;
|
|
401
|
+
// Write Tailwind class inventory after all templates have been rendered (if Tailwind is used)
|
|
402
|
+
if (hasTailwind) {
|
|
403
|
+
const inventorySize = getInventorySize();
|
|
404
|
+
if (inventorySize > 0) {
|
|
405
|
+
await writeTailwindClassInventory(cacheDir);
|
|
406
|
+
logger.info(`📝 Generated Tailwind class inventory (${inventorySize} classes tracked)`);
|
|
407
|
+
}
|
|
408
|
+
// Disable inventory tracking after build
|
|
409
|
+
disableInventoryTracking();
|
|
410
|
+
}
|
|
376
411
|
// Save updated cache manifest
|
|
377
412
|
await saveCacheManifest(cacheDir, manifest);
|
|
378
413
|
// Copy static assets and count them
|
package/dist/core/dev.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/core/dev.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/core/dev.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAmB7D,MAAM,WAAW,gBAAgB;IAC/B,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,SAAS;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb;AA4SD,wBAAsB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC,CA2XxF"}
|
package/dist/core/dev.js
CHANGED
|
@@ -7,7 +7,9 @@ import chokidar from 'chokidar';
|
|
|
7
7
|
import { build } from './build.js';
|
|
8
8
|
import { invalidate } from './invalidate.js';
|
|
9
9
|
import { loadConfig } from '../config/loader.js';
|
|
10
|
-
import { loadCacheManifest, saveCacheManifest } from './isg/index.js';
|
|
10
|
+
import { loadCacheManifest, saveCacheManifest, computeNavigationHash } from './isg/index.js';
|
|
11
|
+
import { loadContent } from './content.js';
|
|
12
|
+
import { buildNavigation } from './navigation.js';
|
|
11
13
|
import { resolveDevPaths, resolveCacheDir, resolvePrettyUrl, createErrorOverlay, parseErrorDetails, TemplateError, } from './utils/index.js';
|
|
12
14
|
import { setEnv, getEnv } from '../env.js';
|
|
13
15
|
import { DEFAULT_DEV_PORT, DEFAULT_DEV_HOST, TEMPLATE_EXTENSION } from '../constants.js';
|
|
@@ -88,8 +90,11 @@ async function performIncrementalRebuild(changedPath, configPath, logger, wsServ
|
|
|
88
90
|
if (changedPath.endsWith(TEMPLATE_EXTENSION) || changedPath.includes('_partials')) {
|
|
89
91
|
await handleTemplateChange(changedPath, configPath, devLogger);
|
|
90
92
|
}
|
|
93
|
+
else if (changedPath.endsWith('.md')) {
|
|
94
|
+
await handleMarkdownChange(changedPath, configPath, devLogger);
|
|
95
|
+
}
|
|
91
96
|
else {
|
|
92
|
-
//
|
|
97
|
+
// Static file changed - use normal rebuild
|
|
93
98
|
await build({
|
|
94
99
|
logger: devLogger,
|
|
95
100
|
force: false,
|
|
@@ -198,6 +203,69 @@ async function handleTemplateChange(templatePath, configPath, logger) {
|
|
|
198
203
|
}
|
|
199
204
|
}
|
|
200
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Handles markdown file changes by comparing navigation hashes.
|
|
208
|
+
* Only performs a full rebuild if navigation structure actually changed.
|
|
209
|
+
* Navigation changes come from frontmatter modifications (title, order, description).
|
|
210
|
+
* Content-only changes use incremental rebuilds.
|
|
211
|
+
*/
|
|
212
|
+
async function handleMarkdownChange(_markdownPath, configPath, logger) {
|
|
213
|
+
const cacheDir = resolveCacheDir();
|
|
214
|
+
try {
|
|
215
|
+
// Load existing cache manifest
|
|
216
|
+
const cacheManifest = await loadCacheManifest(cacheDir);
|
|
217
|
+
if (!cacheManifest || !cacheManifest.navigationHash) {
|
|
218
|
+
// No cache or no navigation hash exists, perform full rebuild
|
|
219
|
+
await build({
|
|
220
|
+
logger,
|
|
221
|
+
force: false,
|
|
222
|
+
clean: false,
|
|
223
|
+
...(configPath && { configPath }),
|
|
224
|
+
});
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// Load config and content to rebuild navigation tree
|
|
228
|
+
const config = await loadConfig(configPath);
|
|
229
|
+
const pages = await loadContent(config);
|
|
230
|
+
const newNavigation = buildNavigation(pages);
|
|
231
|
+
const newNavigationHash = computeNavigationHash(newNavigation);
|
|
232
|
+
// Compare navigation hashes
|
|
233
|
+
if (newNavigationHash !== cacheManifest.navigationHash) {
|
|
234
|
+
// Navigation structure changed - clear cache and force full rebuild
|
|
235
|
+
logger.info?.('📊 Navigation structure changed, performing full rebuild...');
|
|
236
|
+
// Force rebuild bypasses ISG cache entirely
|
|
237
|
+
await build({
|
|
238
|
+
logger,
|
|
239
|
+
force: true, // Force rebuild to bypass cache
|
|
240
|
+
clean: false,
|
|
241
|
+
...(configPath && { configPath }),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
// Navigation unchanged - use incremental rebuild for content changes
|
|
246
|
+
await build({
|
|
247
|
+
logger,
|
|
248
|
+
force: false,
|
|
249
|
+
clean: false,
|
|
250
|
+
...(configPath && { configPath }),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch (_error) {
|
|
255
|
+
try {
|
|
256
|
+
// Fallback to full rebuild
|
|
257
|
+
await build({
|
|
258
|
+
logger,
|
|
259
|
+
force: false,
|
|
260
|
+
clean: false,
|
|
261
|
+
...(configPath && { configPath }),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
catch (fallbackError) {
|
|
265
|
+
throw fallbackError instanceof Error ? fallbackError : new Error(String(fallbackError));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
201
269
|
export async function createDevServer(options = {}) {
|
|
202
270
|
const { port = DEFAULT_DEV_PORT, host = DEFAULT_DEV_HOST, open = false, configPath, logger = {
|
|
203
271
|
info: (msg) => console.log(msg),
|
package/dist/core/isg/deps.js
CHANGED
|
@@ -244,6 +244,27 @@ async function parseTemplateDependencies(content, templatePath, srcDir) {
|
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
|
+
// Look for Stati callable partial patterns: stati.partials.name( or stati.partials['name'](
|
|
248
|
+
// This catches both direct property access and bracket notation with or without arguments
|
|
249
|
+
// Patterns allow for optional whitespace before the opening parenthesis
|
|
250
|
+
const callablePartialPatterns = [
|
|
251
|
+
/stati\.partials\.(\w+)\s*\(/g, // stati.partials.header( or stati.partials.header (
|
|
252
|
+
/stati\.partials\[['"`]([^'"`]+)['"`]\]\s*\(/g, // stati.partials['header']( with whitespace
|
|
253
|
+
];
|
|
254
|
+
for (const pattern of callablePartialPatterns) {
|
|
255
|
+
let match;
|
|
256
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
257
|
+
const partialName = match[1];
|
|
258
|
+
if (partialName) {
|
|
259
|
+
// Resolve the partial by searching for it in underscore directories
|
|
260
|
+
const partialFileName = `${partialName}${TEMPLATE_EXTENSION}`;
|
|
261
|
+
const resolvedPath = await resolveTemplatePathInternal(partialFileName, srcDir, templateDir);
|
|
262
|
+
if (resolvedPath) {
|
|
263
|
+
dependencies.push(resolvedPath);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
247
268
|
return dependencies;
|
|
248
269
|
}
|
|
249
270
|
/**
|
package/dist/core/isg/hash.d.ts
CHANGED
|
@@ -45,4 +45,18 @@ export declare function computeFileHash(filePath: string): Promise<string | null
|
|
|
45
45
|
* ```
|
|
46
46
|
*/
|
|
47
47
|
export declare function computeInputsHash(contentHash: string, depsHashes: string[]): string;
|
|
48
|
+
/**
|
|
49
|
+
* Computes a hash of the navigation tree structure.
|
|
50
|
+
* Used to detect when navigation has changed (title, url, order, children structure).
|
|
51
|
+
*
|
|
52
|
+
* @param navigation - The navigation tree to hash
|
|
53
|
+
* @returns SHA-256 hash as a hex string
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const hash = computeNavigationHash(navigationTree);
|
|
58
|
+
* console.log(hash); // "sha256-abc123..."
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function computeNavigationHash(navigation: unknown[]): string;
|
|
48
62
|
//# sourceMappingURL=hash.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
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,CAiBhG;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"}
|
|
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,CAiBhG;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;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,MAAM,CAqCnE"}
|
package/dist/core/isg/hash.js
CHANGED
|
@@ -97,7 +97,38 @@ export async function computeFileHash(filePath) {
|
|
|
97
97
|
* ```
|
|
98
98
|
*/
|
|
99
99
|
export function computeInputsHash(contentHash, depsHashes) {
|
|
100
|
-
//
|
|
100
|
+
// Sort dependency hashes for consistency
|
|
101
101
|
const sortedDepsHashes = [...depsHashes].sort();
|
|
102
102
|
return createSha256Hash([contentHash, ...sortedDepsHashes]);
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Computes a hash of the navigation tree structure.
|
|
106
|
+
* Used to detect when navigation has changed (title, url, order, children structure).
|
|
107
|
+
*
|
|
108
|
+
* @param navigation - The navigation tree to hash
|
|
109
|
+
* @returns SHA-256 hash as a hex string
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* const hash = computeNavigationHash(navigationTree);
|
|
114
|
+
* console.log(hash); // "sha256-abc123..."
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export function computeNavigationHash(navigation) {
|
|
118
|
+
const normalizeNavNode = (node) => {
|
|
119
|
+
const normalized = {
|
|
120
|
+
title: String(node.title ?? ''),
|
|
121
|
+
url: String(node.url ?? ''),
|
|
122
|
+
};
|
|
123
|
+
if (node.order !== undefined && typeof node.order === 'number') {
|
|
124
|
+
normalized.order = node.order;
|
|
125
|
+
}
|
|
126
|
+
if (node.children && Array.isArray(node.children) && node.children.length > 0) {
|
|
127
|
+
normalized.children = node.children.map((child) => normalizeNavNode(child));
|
|
128
|
+
}
|
|
129
|
+
return normalized;
|
|
130
|
+
};
|
|
131
|
+
const normalizedNav = navigation.map((node) => normalizeNavNode(node));
|
|
132
|
+
const navJson = JSON.stringify(normalizedNav);
|
|
133
|
+
return createSha256Hash(navJson);
|
|
134
|
+
}
|
package/dist/core/isg/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { loadCacheManifest, saveCacheManifest, createEmptyManifest } from './man
|
|
|
10
10
|
export { shouldRebuildPage, createCacheEntry, updateCacheEntry } from './builder.js';
|
|
11
11
|
export { BuildLockManager, withBuildLock } from './build-lock.js';
|
|
12
12
|
export { CircularDependencyError, trackTemplateDependencies, findPartialDependencies, resolveTemplatePath, } from './deps.js';
|
|
13
|
-
export { computeContentHash, computeFileHash, computeInputsHash } from './hash.js';
|
|
13
|
+
export { computeContentHash, computeFileHash, computeInputsHash, computeNavigationHash, } from './hash.js';
|
|
14
14
|
export { getSafeCurrentTime, parseSafeDate, computeEffectiveTTL, computeNextRebuildAt, isPageFrozen, applyAgingRules, } from './ttl.js';
|
|
15
15
|
export { ISGConfigurationError, validateISGConfig, validatePageISGOverrides, extractNumericOverride, } from './validation.js';
|
|
16
16
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/isg/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAG1F,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrF,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGlE,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,WAAW,CAAC;AAGnB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/isg/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAG1F,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrF,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGlE,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,eAAe,GAChB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC"}
|
package/dist/core/isg/index.js
CHANGED
|
@@ -15,7 +15,7 @@ export { BuildLockManager, withBuildLock } from './build-lock.js';
|
|
|
15
15
|
// Dependency tracking
|
|
16
16
|
export { CircularDependencyError, trackTemplateDependencies, findPartialDependencies, resolveTemplatePath, } from './deps.js';
|
|
17
17
|
// Hash computation
|
|
18
|
-
export { computeContentHash, computeFileHash, computeInputsHash } from './hash.js';
|
|
18
|
+
export { computeContentHash, computeFileHash, computeInputsHash, computeNavigationHash, } from './hash.js';
|
|
19
19
|
// TTL and aging
|
|
20
20
|
export { getSafeCurrentTime, parseSafeDate, computeEffectiveTTL, computeNextRebuildAt, isPageFrozen, applyAgingRules, } from './ttl.js';
|
|
21
21
|
// Validation
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,CAsGvF;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"}
|
|
@@ -71,7 +71,13 @@ export async function loadCacheManifest(cacheDir) {
|
|
|
71
71
|
if (invalidEntryCount > 0) {
|
|
72
72
|
console.warn(`Removed ${invalidEntryCount} invalid cache entries`);
|
|
73
73
|
}
|
|
74
|
-
|
|
74
|
+
// Return manifest with entries AND navigationHash (if present)
|
|
75
|
+
const resultManifest = { entries: validatedEntries };
|
|
76
|
+
// Preserve navigationHash if it exists
|
|
77
|
+
if (typeof manifestObj.navigationHash === 'string') {
|
|
78
|
+
resultManifest.navigationHash = manifestObj.navigationHash;
|
|
79
|
+
}
|
|
80
|
+
return resultManifest;
|
|
75
81
|
}
|
|
76
82
|
catch (error) {
|
|
77
83
|
const nodeError = error;
|
|
@@ -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;AAG1B,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAkB,MAAM,mBAAmB,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;AAmNzF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,GAAG,CAW7D;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,CAsLjB"}
|
package/dist/core/templates.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Eta } from 'eta';
|
|
|
2
2
|
import { join, dirname, relative, basename, posix } from 'path';
|
|
3
3
|
import glob from 'fast-glob';
|
|
4
4
|
import { TEMPLATE_EXTENSION } from '../constants.js';
|
|
5
|
-
import { getStatiVersion, isCollectionIndexPage, discoverLayout, getCollectionPathForPage, resolveSrcDir, createTemplateError, createValidatingPartialsProxy, propValue, } from './utils/index.js';
|
|
5
|
+
import { getStatiVersion, isCollectionIndexPage, discoverLayout, getCollectionPathForPage, resolveSrcDir, createTemplateError, createValidatingPartialsProxy, propValue, wrapPartialsAsCallable, createNavigationHelpers, } from './utils/index.js';
|
|
6
6
|
import { getEnv } from '../env.js';
|
|
7
7
|
import { generateSEO } from '../seo/index.js';
|
|
8
8
|
/**
|
|
@@ -100,6 +100,25 @@ function buildCollectionData(currentPage, allPages) {
|
|
|
100
100
|
},
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Builds collection data for a child page (showing parent collection data).
|
|
105
|
+
* Finds the parent collection and returns its data.
|
|
106
|
+
*
|
|
107
|
+
* @param currentPage - The current page being rendered
|
|
108
|
+
* @param allPages - All pages in the site
|
|
109
|
+
* @returns Collection data for the parent collection or undefined
|
|
110
|
+
*/
|
|
111
|
+
function buildParentCollectionData(currentPage, allPages) {
|
|
112
|
+
// Get the parent collection path
|
|
113
|
+
const parentPath = getCollectionPathForPage(currentPage.url);
|
|
114
|
+
// Find the index page for the parent collection
|
|
115
|
+
const parentIndexPage = allPages.find((p) => p.url === parentPath);
|
|
116
|
+
if (parentIndexPage) {
|
|
117
|
+
// Build collection data for the parent
|
|
118
|
+
return buildCollectionData(parentIndexPage, allPages);
|
|
119
|
+
}
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
103
122
|
/**
|
|
104
123
|
* Discovers partials in the hierarchy for a given page path.
|
|
105
124
|
* Scans all parent directories for folders starting with underscore.
|
|
@@ -168,15 +187,26 @@ export async function renderPage(page, body, config, eta, navigation, allPages)
|
|
|
168
187
|
const srcDir = resolveSrcDir(config);
|
|
169
188
|
const relativePath = relative(srcDir, page.sourcePath);
|
|
170
189
|
const partialPaths = await discoverPartials(relativePath, config);
|
|
171
|
-
// Build collection data
|
|
190
|
+
// Build collection data based on page type
|
|
172
191
|
let collectionData;
|
|
173
192
|
const isIndexPage = allPages && isCollectionIndexPage(page, allPages);
|
|
174
|
-
if (
|
|
175
|
-
|
|
193
|
+
if (allPages) {
|
|
194
|
+
if (isIndexPage) {
|
|
195
|
+
// For index pages, show their own collection data
|
|
196
|
+
collectionData = buildCollectionData(page, allPages);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// For child pages, show parent collection data
|
|
200
|
+
collectionData = buildParentCollectionData(page, allPages);
|
|
201
|
+
}
|
|
176
202
|
}
|
|
177
203
|
// Discover the appropriate layout using hierarchical layout.eta convention
|
|
178
204
|
// Pass isIndexPage flag to enable index.eta lookup for aggregation pages
|
|
179
205
|
const layoutPath = await discoverLayout(relativePath, config, page.frontMatter.layout, isIndexPage);
|
|
206
|
+
// Create navigation helpers
|
|
207
|
+
const navTree = navigation || [];
|
|
208
|
+
const navHelpers = createNavigationHelpers(navTree, page);
|
|
209
|
+
const currentNavNode = navHelpers.getCurrentNode();
|
|
180
210
|
// Create base context for partial rendering
|
|
181
211
|
const baseContext = {
|
|
182
212
|
site: config.site,
|
|
@@ -185,10 +215,11 @@ export async function renderPage(page, body, config, eta, navigation, allPages)
|
|
|
185
215
|
path: page.url,
|
|
186
216
|
url: page.url, // Add url property for compatibility
|
|
187
217
|
content: body,
|
|
218
|
+
navNode: currentNavNode, // Add current page's navigation node
|
|
188
219
|
},
|
|
189
220
|
content: body,
|
|
190
|
-
|
|
191
|
-
collection: collectionData, // Add collection data
|
|
221
|
+
nav: navHelpers, // Replace navigation with nav helpers
|
|
222
|
+
collection: collectionData, // Add collection data
|
|
192
223
|
// Add custom filters to context
|
|
193
224
|
...(config.eta?.filters || {}),
|
|
194
225
|
generator: {
|
|
@@ -223,9 +254,11 @@ export async function renderPage(page, body, config, eta, navigation, allPages)
|
|
|
223
254
|
try {
|
|
224
255
|
// Create context with all previously rendered partials available
|
|
225
256
|
const combinedPartials = { ...renderedPartials, ...passRenderedPartials };
|
|
257
|
+
// Wrap partials as callable before passing to validation proxy
|
|
258
|
+
const callablePartials = wrapPartialsAsCallable(eta, combinedPartials, partialPaths, baseContext);
|
|
226
259
|
const partialContext = {
|
|
227
260
|
...baseContext,
|
|
228
|
-
partials: createValidatingPartialsProxy(
|
|
261
|
+
partials: createValidatingPartialsProxy(callablePartials), // Include both previous and current pass partials with validation
|
|
229
262
|
};
|
|
230
263
|
const renderedContent = await eta.renderAsync(partialPath, partialContext);
|
|
231
264
|
passRenderedPartials[partialName] = renderedContent;
|
|
@@ -268,9 +301,11 @@ export async function renderPage(page, body, config, eta, navigation, allPages)
|
|
|
268
301
|
break;
|
|
269
302
|
}
|
|
270
303
|
}
|
|
304
|
+
// Wrap final rendered partials as callable before passing to layout context
|
|
305
|
+
const callablePartials = wrapPartialsAsCallable(eta, renderedPartials, partialPaths, baseContext);
|
|
271
306
|
const context = {
|
|
272
307
|
...baseContext,
|
|
273
|
-
partials: createValidatingPartialsProxy(
|
|
308
|
+
partials: createValidatingPartialsProxy(callablePartials), // Add rendered partials with validation
|
|
274
309
|
};
|
|
275
310
|
try {
|
|
276
311
|
if (!layoutPath) {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Eta } from 'eta';
|
|
2
|
+
/**
|
|
3
|
+
* Type definition for a callable partial function.
|
|
4
|
+
* Can be called with optional props or used directly as a value.
|
|
5
|
+
*/
|
|
6
|
+
export type CallablePartial = {
|
|
7
|
+
(props?: Record<string, unknown>): string;
|
|
8
|
+
toString(): string;
|
|
9
|
+
valueOf(): string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Creates a callable partial that can be used both as a value and as a function.
|
|
13
|
+
* This enables both syntaxes:
|
|
14
|
+
* - Direct usage: <%~ stati.partials.header %>
|
|
15
|
+
* - With props: <%~ stati.partials.hero({ title: 'Hello' }) %>
|
|
16
|
+
*
|
|
17
|
+
* @param eta - The Eta template engine instance
|
|
18
|
+
* @param partialPath - Absolute path to the partial template file
|
|
19
|
+
* @param baseContext - The base template context (without props)
|
|
20
|
+
* @param renderedContent - Pre-rendered content for the no-props case
|
|
21
|
+
* @returns A callable partial function
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const callable = makeCallablePartial(eta, '/path/to/partial.eta', baseContext, '<div>Header</div>');
|
|
26
|
+
*
|
|
27
|
+
* // Use without props (returns pre-rendered content)
|
|
28
|
+
* const html1 = callable.toString(); // '<div>Header</div>'
|
|
29
|
+
*
|
|
30
|
+
* // Use with props (re-renders with merged context)
|
|
31
|
+
* const html2 = callable({ title: 'Custom Title' }); // Renders with custom props
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function makeCallablePartial(eta: Eta, partialPath: string, baseContext: Record<string, unknown>, renderedContent: string): CallablePartial;
|
|
35
|
+
/**
|
|
36
|
+
* Wraps all partials in a record with callable partial wrappers.
|
|
37
|
+
* This allows partials to be used both as values and as functions.
|
|
38
|
+
*
|
|
39
|
+
* @param eta - The Eta template engine instance
|
|
40
|
+
* @param partials - Record mapping partial names to their rendered content
|
|
41
|
+
* @param partialPaths - Record mapping partial names to their absolute file paths
|
|
42
|
+
* @param baseContext - The base template context (without props)
|
|
43
|
+
* @returns Record of callable partials
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const callablePartials = wrapPartialsAsCallable(
|
|
48
|
+
* eta,
|
|
49
|
+
* { header: '<div>Header</div>', footer: '<div>Footer</div>' },
|
|
50
|
+
* { header: '/path/to/header.eta', footer: '/path/to/footer.eta' },
|
|
51
|
+
* baseContext
|
|
52
|
+
* );
|
|
53
|
+
*
|
|
54
|
+
* // Both syntaxes work
|
|
55
|
+
* callablePartials.header.toString(); // Direct usage
|
|
56
|
+
* callablePartials.header({ title: 'Custom' }); // With props
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare function wrapPartialsAsCallable(eta: Eta, partials: Record<string, string>, partialPaths: Record<string, string>, baseContext: Record<string, unknown>): Record<string, CallablePartial>;
|
|
60
|
+
//# sourceMappingURL=callable-partials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callable-partials.d.ts","sourceRoot":"","sources":["../../../src/core/utils/callable-partials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;IAC1C,QAAQ,IAAI,MAAM,CAAC;IACnB,OAAO,IAAI,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,GAAG,EACR,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,eAAe,EAAE,MAAM,GACtB,eAAe,CAqDjB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAcjC"}
|