@stati/core 1.27.1 → 1.27.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/core/build.js +5 -5
- package/dist/core/isg/builder.d.ts +9 -6
- package/dist/core/isg/builder.d.ts.map +1 -1
- package/dist/core/isg/builder.js +21 -13
- package/dist/core/isg/deps.d.ts +14 -2
- package/dist/core/isg/deps.d.ts.map +1 -1
- package/dist/core/isg/deps.js +24 -7
- package/dist/core/utils/template-discovery.utils.d.ts +8 -6
- package/dist/core/utils/template-discovery.utils.d.ts.map +1 -1
- package/dist/core/utils/template-discovery.utils.js +43 -12
- package/package.json +1 -1
package/dist/core/build.js
CHANGED
|
@@ -273,7 +273,7 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
273
273
|
const shouldRebuild = options.force ||
|
|
274
274
|
config.isg?.enabled === false ||
|
|
275
275
|
!(await pathExists(outputPath)) ||
|
|
276
|
-
(await shouldRebuildPage(page, existingEntry, config, buildTime, renderInputsHash));
|
|
276
|
+
(await shouldRebuildPage(page, existingEntry, config, pages, buildTime, renderInputsHash));
|
|
277
277
|
const shouldRebuildTime = performance.now() - shouldRebuildStart;
|
|
278
278
|
totalShouldRebuildTime += shouldRebuildTime;
|
|
279
279
|
if (!shouldRebuild) {
|
|
@@ -357,10 +357,10 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
357
357
|
if (!options.skipCacheUpdate) {
|
|
358
358
|
const cacheEntryStart = performance.now();
|
|
359
359
|
if (existingEntry) {
|
|
360
|
-
manifest.entries[cacheKey] = await updateCacheEntry(existingEntry, page, config, buildTime, false, renderInputsHash);
|
|
360
|
+
manifest.entries[cacheKey] = await updateCacheEntry(existingEntry, page, config, pages, buildTime, false, renderInputsHash);
|
|
361
361
|
}
|
|
362
362
|
else {
|
|
363
|
-
manifest.entries[cacheKey] = await createCacheEntry(page, config, buildTime, renderInputsHash);
|
|
363
|
+
manifest.entries[cacheKey] = await createCacheEntry(page, config, pages, buildTime, renderInputsHash);
|
|
364
364
|
}
|
|
365
365
|
totalCacheEntryTime += performance.now() - cacheEntryStart;
|
|
366
366
|
}
|
|
@@ -393,11 +393,11 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
|
|
|
393
393
|
for (const { cacheKey, page, existingEntry, renderInputsHash } of pendingCacheUpdates) {
|
|
394
394
|
if (existingEntry) {
|
|
395
395
|
// Fast update: reuse existing deps, just update hashes and timestamp
|
|
396
|
-
manifest.entries[cacheKey] = await updateCacheEntry(existingEntry, page, config, buildTime, true, // fastUpdate = true in dev mode
|
|
396
|
+
manifest.entries[cacheKey] = await updateCacheEntry(existingEntry, page, config, pages, buildTime, true, // fastUpdate = true in dev mode
|
|
397
397
|
renderInputsHash);
|
|
398
398
|
}
|
|
399
399
|
else {
|
|
400
|
-
manifest.entries[cacheKey] = await createCacheEntry(page, config, buildTime, renderInputsHash);
|
|
400
|
+
manifest.entries[cacheKey] = await createCacheEntry(page, config, pages, buildTime, renderInputsHash);
|
|
401
401
|
}
|
|
402
402
|
}
|
|
403
403
|
totalCacheEntryTime = performance.now() - cacheEntryStart;
|
|
@@ -7,34 +7,36 @@ import type { PageModel, CacheEntry, StatiConfig } from '../../types/index.js';
|
|
|
7
7
|
* @param page - The page model to check
|
|
8
8
|
* @param entry - Existing cache entry, or undefined if not cached
|
|
9
9
|
* @param config - Stati configuration
|
|
10
|
+
* @param allPages - All pages in the site, used to resolve the same layout as rendering
|
|
10
11
|
* @param now - Current date/time
|
|
11
12
|
* @param renderInputsHash - Optional hash of asset URLs and injection settings
|
|
12
13
|
* @returns True if the page should be rebuilt
|
|
13
14
|
*
|
|
14
15
|
* @example
|
|
15
16
|
* ```typescript
|
|
16
|
-
* const shouldRebuild = await shouldRebuildPage(page, cacheEntry, config, new Date());
|
|
17
|
+
* const shouldRebuild = await shouldRebuildPage(page, cacheEntry, config, pages, new Date());
|
|
17
18
|
* if (shouldRebuild) {
|
|
18
19
|
* console.log('Page needs rebuilding');
|
|
19
20
|
* }
|
|
20
21
|
* ```
|
|
21
22
|
*/
|
|
22
|
-
export declare function shouldRebuildPage(page: PageModel, entry: CacheEntry | undefined, config: StatiConfig, now: Date, renderInputsHash?: string): Promise<boolean>;
|
|
23
|
+
export declare function shouldRebuildPage(page: PageModel, entry: CacheEntry | undefined, config: StatiConfig, allPages: PageModel[], now: Date, renderInputsHash?: string): Promise<boolean>;
|
|
23
24
|
/**
|
|
24
25
|
* Creates a new cache entry for a page after it has been rendered.
|
|
25
26
|
*
|
|
26
27
|
* @param page - The page model
|
|
27
28
|
* @param config - Stati configuration
|
|
29
|
+
* @param allPages - All pages in the site, used to resolve the same layout as rendering
|
|
28
30
|
* @param renderedAt - When the page was rendered
|
|
29
31
|
* @param renderInputsHash - Optional hash of asset URLs and injection settings
|
|
30
32
|
* @returns New cache entry
|
|
31
33
|
*
|
|
32
34
|
* @example
|
|
33
35
|
* ```typescript
|
|
34
|
-
* const entry = await createCacheEntry(page, config, new Date());
|
|
36
|
+
* const entry = await createCacheEntry(page, config, pages, new Date());
|
|
35
37
|
* ```
|
|
36
38
|
*/
|
|
37
|
-
export declare function createCacheEntry(page: PageModel, config: StatiConfig, renderedAt: Date, renderInputsHash?: string): Promise<CacheEntry>;
|
|
39
|
+
export declare function createCacheEntry(page: PageModel, config: StatiConfig, allPages: PageModel[], renderedAt: Date, renderInputsHash?: string): Promise<CacheEntry>;
|
|
38
40
|
/**
|
|
39
41
|
* Updates an existing cache entry with new information after rebuilding.
|
|
40
42
|
* In dev mode, this is optimized to reuse the existing deps array since
|
|
@@ -43,6 +45,7 @@ export declare function createCacheEntry(page: PageModel, config: StatiConfig, r
|
|
|
43
45
|
* @param entry - Existing cache entry
|
|
44
46
|
* @param page - The page model
|
|
45
47
|
* @param config - Stati configuration
|
|
48
|
+
* @param allPages - All pages in the site, used to resolve the same layout as rendering
|
|
46
49
|
* @param renderedAt - When the page was rendered
|
|
47
50
|
* @param fastUpdate - If true, skip expensive dep tracking (dev mode optimization)
|
|
48
51
|
* @param renderInputsHash - Optional hash of asset URLs and injection settings
|
|
@@ -50,8 +53,8 @@ export declare function createCacheEntry(page: PageModel, config: StatiConfig, r
|
|
|
50
53
|
*
|
|
51
54
|
* @example
|
|
52
55
|
* ```typescript
|
|
53
|
-
* const updatedEntry = await updateCacheEntry(existingEntry, page, config, new Date());
|
|
56
|
+
* const updatedEntry = await updateCacheEntry(existingEntry, page, config, pages, new Date());
|
|
54
57
|
* ```
|
|
55
58
|
*/
|
|
56
|
-
export declare function updateCacheEntry(entry: CacheEntry, page: PageModel, config: StatiConfig, renderedAt: Date, fastUpdate?: boolean, renderInputsHash?: string): Promise<CacheEntry>;
|
|
59
|
+
export declare function updateCacheEntry(entry: CacheEntry, page: PageModel, config: StatiConfig, allPages: PageModel[], renderedAt: Date, fastUpdate?: boolean, renderInputsHash?: string): Promise<CacheEntry>;
|
|
57
60
|
//# sourceMappingURL=builder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../../src/core/isg/builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAqE/E
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../../src/core/isg/builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAqE/E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,SAAS,EAAE,EACrB,GAAG,EAAE,IAAI,EACT,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,OAAO,CAAC,CAoKlB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,SAAS,EAAE,EACrB,UAAU,EAAE,IAAI,EAChB,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,UAAU,CAAC,CAqFrB;AAkDD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,SAAS,EAAE,EACrB,UAAU,EAAE,IAAI,EAChB,UAAU,UAAQ,EAClB,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,UAAU,CAAC,CAmDrB"}
|
package/dist/core/isg/builder.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { computeContentHash, computeFileHash, computeInputsHash } from './hash.js';
|
|
2
|
-
import { trackTemplateDependencies } from './deps.js';
|
|
2
|
+
import { trackTemplateDependencies, resolveLayoutDependency } from './deps.js';
|
|
3
3
|
import { computeEffectiveTTL, computeNextRebuildAt, isPageFrozen } from './ttl.js';
|
|
4
4
|
import { validatePageISGOverrides, extractNumericOverride } from './validation.js';
|
|
5
5
|
import { getFrontMatterDate } from '../utils/index.js';
|
|
@@ -65,19 +65,20 @@ function isValidCacheEntry(entry) {
|
|
|
65
65
|
* @param page - The page model to check
|
|
66
66
|
* @param entry - Existing cache entry, or undefined if not cached
|
|
67
67
|
* @param config - Stati configuration
|
|
68
|
+
* @param allPages - All pages in the site, used to resolve the same layout as rendering
|
|
68
69
|
* @param now - Current date/time
|
|
69
70
|
* @param renderInputsHash - Optional hash of asset URLs and injection settings
|
|
70
71
|
* @returns True if the page should be rebuilt
|
|
71
72
|
*
|
|
72
73
|
* @example
|
|
73
74
|
* ```typescript
|
|
74
|
-
* const shouldRebuild = await shouldRebuildPage(page, cacheEntry, config, new Date());
|
|
75
|
+
* const shouldRebuild = await shouldRebuildPage(page, cacheEntry, config, pages, new Date());
|
|
75
76
|
* if (shouldRebuild) {
|
|
76
77
|
* console.log('Page needs rebuilding');
|
|
77
78
|
* }
|
|
78
79
|
* ```
|
|
79
80
|
*/
|
|
80
|
-
export async function shouldRebuildPage(page, entry, config, now, renderInputsHash) {
|
|
81
|
+
export async function shouldRebuildPage(page, entry, config, allPages, now, renderInputsHash) {
|
|
81
82
|
if (config.isg?.enabled === false) {
|
|
82
83
|
return true;
|
|
83
84
|
}
|
|
@@ -96,7 +97,7 @@ export async function shouldRebuildPage(page, entry, config, now, renderInputsHa
|
|
|
96
97
|
// Track dependencies with error handling
|
|
97
98
|
let deps;
|
|
98
99
|
try {
|
|
99
|
-
deps = await trackTemplateDependencies(page, config);
|
|
100
|
+
deps = await trackTemplateDependencies(page, config, allPages);
|
|
100
101
|
}
|
|
101
102
|
catch (error) {
|
|
102
103
|
if (error instanceof Error && error.name === 'CircularDependencyError') {
|
|
@@ -211,16 +212,17 @@ export async function shouldRebuildPage(page, entry, config, now, renderInputsHa
|
|
|
211
212
|
*
|
|
212
213
|
* @param page - The page model
|
|
213
214
|
* @param config - Stati configuration
|
|
215
|
+
* @param allPages - All pages in the site, used to resolve the same layout as rendering
|
|
214
216
|
* @param renderedAt - When the page was rendered
|
|
215
217
|
* @param renderInputsHash - Optional hash of asset URLs and injection settings
|
|
216
218
|
* @returns New cache entry
|
|
217
219
|
*
|
|
218
220
|
* @example
|
|
219
221
|
* ```typescript
|
|
220
|
-
* const entry = await createCacheEntry(page, config, new Date());
|
|
222
|
+
* const entry = await createCacheEntry(page, config, pages, new Date());
|
|
221
223
|
* ```
|
|
222
224
|
*/
|
|
223
|
-
export async function createCacheEntry(page, config, renderedAt, renderInputsHash) {
|
|
225
|
+
export async function createCacheEntry(page, config, allPages, renderedAt, renderInputsHash) {
|
|
224
226
|
const timings = {};
|
|
225
227
|
let start = performance.now();
|
|
226
228
|
// Validate page-level ISG overrides first
|
|
@@ -232,7 +234,7 @@ export async function createCacheEntry(page, config, renderedAt, renderInputsHas
|
|
|
232
234
|
timings.contentHash = performance.now() - start;
|
|
233
235
|
// Track all template dependencies
|
|
234
236
|
start = performance.now();
|
|
235
|
-
const deps = await trackTemplateDependencies(page, config);
|
|
237
|
+
const deps = await trackTemplateDependencies(page, config, allPages);
|
|
236
238
|
timings.trackDeps = performance.now() - start;
|
|
237
239
|
// Compute hashes for all dependencies
|
|
238
240
|
start = performance.now();
|
|
@@ -339,6 +341,7 @@ async function hasTemplateStructureChanged(entry) {
|
|
|
339
341
|
* @param entry - Existing cache entry
|
|
340
342
|
* @param page - The page model
|
|
341
343
|
* @param config - Stati configuration
|
|
344
|
+
* @param allPages - All pages in the site, used to resolve the same layout as rendering
|
|
342
345
|
* @param renderedAt - When the page was rendered
|
|
343
346
|
* @param fastUpdate - If true, skip expensive dep tracking (dev mode optimization)
|
|
344
347
|
* @param renderInputsHash - Optional hash of asset URLs and injection settings
|
|
@@ -346,19 +349,24 @@ async function hasTemplateStructureChanged(entry) {
|
|
|
346
349
|
*
|
|
347
350
|
* @example
|
|
348
351
|
* ```typescript
|
|
349
|
-
* const updatedEntry = await updateCacheEntry(existingEntry, page, config, new Date());
|
|
352
|
+
* const updatedEntry = await updateCacheEntry(existingEntry, page, config, pages, new Date());
|
|
350
353
|
* ```
|
|
351
354
|
*/
|
|
352
|
-
export async function updateCacheEntry(entry, page, config, renderedAt, fastUpdate = false, renderInputsHash) {
|
|
355
|
+
export async function updateCacheEntry(entry, page, config, allPages, renderedAt, fastUpdate = false, renderInputsHash) {
|
|
353
356
|
// In fast update mode (dev), reuse existing deps to avoid expensive tracking
|
|
354
357
|
if (fastUpdate && entry.deps.length > 0) {
|
|
355
358
|
// Detect if template structure has changed (new includes/extends added)
|
|
356
359
|
// by checking if the layout file has been modified or if template content
|
|
357
360
|
// contains different dependency patterns
|
|
358
361
|
const structureChanged = await hasTemplateStructureChanged(entry);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
+
// The page can also switch layouts without any template file changing, for example
|
|
363
|
+
// when a collection gains its first child page or a front matter layout is edited.
|
|
364
|
+
// deps[0] is always the layout, so compare it against the layout used now.
|
|
365
|
+
const layoutChanged = !structureChanged &&
|
|
366
|
+
(await resolveLayoutDependency(page, config, allPages)) !== entry.deps[0];
|
|
367
|
+
if (structureChanged || layoutChanged) {
|
|
368
|
+
// Template structure or layout changed - fall through to full dependency tracking
|
|
369
|
+
// This ensures we pick up new includes/extends and the newly selected layout
|
|
362
370
|
}
|
|
363
371
|
else {
|
|
364
372
|
// Structure unchanged - safe to reuse existing deps
|
|
@@ -381,7 +389,7 @@ export async function updateCacheEntry(entry, page, config, renderedAt, fastUpda
|
|
|
381
389
|
}
|
|
382
390
|
}
|
|
383
391
|
// Full update - create new entry and preserve original publishedAt
|
|
384
|
-
const newEntry = await createCacheEntry(page, config, renderedAt, renderInputsHash);
|
|
392
|
+
const newEntry = await createCacheEntry(page, config, allPages, renderedAt, renderInputsHash);
|
|
385
393
|
// Preserve original publishedAt if no new one is specified
|
|
386
394
|
if (!newEntry.publishedAt && entry.publishedAt) {
|
|
387
395
|
newEntry.publishedAt = entry.publishedAt;
|
package/dist/core/isg/deps.d.ts
CHANGED
|
@@ -22,13 +22,14 @@ export declare class CircularDependencyError extends Error {
|
|
|
22
22
|
*
|
|
23
23
|
* @param page - The page model to track dependencies for
|
|
24
24
|
* @param config - Stati configuration
|
|
25
|
+
* @param allPages - All pages in the site, used to detect collection index pages the same way rendering does
|
|
25
26
|
* @returns Array of absolute paths to actually-used template files (POSIX format)
|
|
26
27
|
* @throws {CircularDependencyError} When circular dependencies are detected in templates
|
|
27
28
|
*
|
|
28
29
|
* @example
|
|
29
30
|
* ```typescript
|
|
30
31
|
* try {
|
|
31
|
-
* const deps = await trackTemplateDependencies(page, config);
|
|
32
|
+
* const deps = await trackTemplateDependencies(page, config, allPages);
|
|
32
33
|
* console.log(`Page depends on ${deps.length} template files`);
|
|
33
34
|
* } catch (error) {
|
|
34
35
|
* if (error instanceof CircularDependencyError) {
|
|
@@ -37,7 +38,18 @@ export declare class CircularDependencyError extends Error {
|
|
|
37
38
|
* }
|
|
38
39
|
* ```
|
|
39
40
|
*/
|
|
40
|
-
export declare function trackTemplateDependencies(page: PageModel, config: StatiConfig): Promise<string[]>;
|
|
41
|
+
export declare function trackTemplateDependencies(page: PageModel, config: StatiConfig, allPages: PageModel[]): Promise<string[]>;
|
|
42
|
+
/**
|
|
43
|
+
* Resolves the layout a page renders with, in the form stored as the first entry of its
|
|
44
|
+
* cache dependencies. Uses the same lookup as rendering, so callers can tell whether the
|
|
45
|
+
* page's layout has changed since its dependencies were recorded.
|
|
46
|
+
*
|
|
47
|
+
* @param page - The page model to resolve the layout for
|
|
48
|
+
* @param config - Stati configuration
|
|
49
|
+
* @param allPages - All pages in the site, used to detect collection index pages the same way rendering does
|
|
50
|
+
* @returns Absolute path to the layout file (POSIX format), or null if no layout applies
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveLayoutDependency(page: PageModel, config: StatiConfig, allPages: PageModel[]): Promise<string | null>;
|
|
41
53
|
/**
|
|
42
54
|
* Resolves a template name to its file path.
|
|
43
55
|
* Used for explicit layout specifications in front matter.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../../src/core/isg/deps.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAenE;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAG7C;AAED;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,eAAe,EAAE,MAAM,EAAE;gBAAzB,eAAe,EAAE,MAAM,EAAE,EACzC,OAAO,EAAE,MAAM;CAKlB;AAED
|
|
1
|
+
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../../src/core/isg/deps.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAenE;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAG7C;AAED;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,eAAe,EAAE,MAAM,EAAE;gBAAzB,eAAe,EAAE,MAAM,EAAE,EACzC,OAAO,EAAE,MAAM;CAKlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,SAAS,EAAE,GACpB,OAAO,CAAC,MAAM,EAAE,CAAC,CAuCnB;AAED;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,SAAS,EAAE,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAexB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CASxB"}
|
package/dist/core/isg/deps.js
CHANGED
|
@@ -42,13 +42,14 @@ export class CircularDependencyError extends Error {
|
|
|
42
42
|
*
|
|
43
43
|
* @param page - The page model to track dependencies for
|
|
44
44
|
* @param config - Stati configuration
|
|
45
|
+
* @param allPages - All pages in the site, used to detect collection index pages the same way rendering does
|
|
45
46
|
* @returns Array of absolute paths to actually-used template files (POSIX format)
|
|
46
47
|
* @throws {CircularDependencyError} When circular dependencies are detected in templates
|
|
47
48
|
*
|
|
48
49
|
* @example
|
|
49
50
|
* ```typescript
|
|
50
51
|
* try {
|
|
51
|
-
* const deps = await trackTemplateDependencies(page, config);
|
|
52
|
+
* const deps = await trackTemplateDependencies(page, config, allPages);
|
|
52
53
|
* console.log(`Page depends on ${deps.length} template files`);
|
|
53
54
|
* } catch (error) {
|
|
54
55
|
* if (error instanceof CircularDependencyError) {
|
|
@@ -57,7 +58,7 @@ export class CircularDependencyError extends Error {
|
|
|
57
58
|
* }
|
|
58
59
|
* ```
|
|
59
60
|
*/
|
|
60
|
-
export async function trackTemplateDependencies(page, config) {
|
|
61
|
+
export async function trackTemplateDependencies(page, config, allPages) {
|
|
61
62
|
// Early return if required config values are missing
|
|
62
63
|
if (!config.srcDir) {
|
|
63
64
|
console.warn('Config srcDir is missing, cannot track template dependencies');
|
|
@@ -65,16 +66,13 @@ export async function trackTemplateDependencies(page, config) {
|
|
|
65
66
|
}
|
|
66
67
|
const deps = [];
|
|
67
68
|
const srcDir = resolveSrcDir(config);
|
|
68
|
-
const relativePath = relative(srcDir, page.sourcePath);
|
|
69
69
|
// Track dependencies with circular detection
|
|
70
70
|
// The visited set will contain all templates that are actually used (not just accessible)
|
|
71
71
|
const visited = new Set();
|
|
72
72
|
const currentPath = new Set();
|
|
73
73
|
// 1. Find the layout file that will be used for this page
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
// Normalize to POSIX format for consistent manifest output across platforms
|
|
77
|
-
const absoluteLayoutPath = posix.join(srcDir.replace(/\\/g, '/'), layoutPath);
|
|
74
|
+
const absoluteLayoutPath = await resolveLayoutDependency(page, config, allPages);
|
|
75
|
+
if (absoluteLayoutPath) {
|
|
78
76
|
deps.push(absoluteLayoutPath);
|
|
79
77
|
// Recursively traverse the template dependency tree
|
|
80
78
|
// This populates 'visited' with all actually-used templates (not just accessible ones)
|
|
@@ -93,6 +91,25 @@ export async function trackTemplateDependencies(page, config) {
|
|
|
93
91
|
}
|
|
94
92
|
return deps;
|
|
95
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolves the layout a page renders with, in the form stored as the first entry of its
|
|
96
|
+
* cache dependencies. Uses the same lookup as rendering, so callers can tell whether the
|
|
97
|
+
* page's layout has changed since its dependencies were recorded.
|
|
98
|
+
*
|
|
99
|
+
* @param page - The page model to resolve the layout for
|
|
100
|
+
* @param config - Stati configuration
|
|
101
|
+
* @param allPages - All pages in the site, used to detect collection index pages the same way rendering does
|
|
102
|
+
* @returns Absolute path to the layout file (POSIX format), or null if no layout applies
|
|
103
|
+
*/
|
|
104
|
+
export async function resolveLayoutDependency(page, config, allPages) {
|
|
105
|
+
if (!config.srcDir) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const srcDir = resolveSrcDir(config);
|
|
109
|
+
const layoutPath = await discoverLayout(relative(srcDir, page.sourcePath), config, page.frontMatter.layout, isCollectionIndexPage(page, allPages));
|
|
110
|
+
// Normalize to POSIX format for consistent manifest output across platforms
|
|
111
|
+
return layoutPath ? posix.join(srcDir.replace(/\\/g, '/'), layoutPath) : null;
|
|
112
|
+
}
|
|
96
113
|
/**
|
|
97
114
|
* Resolves a template name to its file path.
|
|
98
115
|
* Used for explicit layout specifications in front matter.
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import type { StatiConfig, PageModel } from '../../types/index.js';
|
|
2
|
-
/**
|
|
3
|
-
* Shared template discovery utilities.
|
|
4
|
-
* Extracted from templates.ts and isg/deps.ts to eliminate duplication.
|
|
5
|
-
*/
|
|
6
2
|
/**
|
|
7
3
|
* Determines if the given page is a collection index page.
|
|
8
4
|
* A collection index page is one whose URL matches a directory path that contains other pages.
|
|
9
5
|
*
|
|
6
|
+
* Rendering and ISG dependency tracking must both call this with the same page list,
|
|
7
|
+
* otherwise they can pick different layouts for the same page.
|
|
8
|
+
*
|
|
9
|
+
* Page URLs are read once per `allPages` array, at the first call; see
|
|
10
|
+
* `directoryPrefixCache` for when changes to the pages are picked up.
|
|
11
|
+
*
|
|
10
12
|
* @param page - The page to check
|
|
11
|
-
* @param allPages - All pages in the site (
|
|
13
|
+
* @param allPages - All pages in the site (treated as unchanging after the first call)
|
|
12
14
|
* @returns True if the page is a collection index page
|
|
13
15
|
*/
|
|
14
|
-
export declare function isCollectionIndexPage(page: PageModel, allPages
|
|
16
|
+
export declare function isCollectionIndexPage(page: PageModel, allPages: PageModel[]): boolean;
|
|
15
17
|
/**
|
|
16
18
|
* Discovers layout files by searching up the directory hierarchy.
|
|
17
19
|
* Supports both explicit layout specification and automatic discovery.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template-discovery.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/template-discovery.utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"template-discovery.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/template-discovery.utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAoDnE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,CAUrF;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,WAAW,EACnB,cAAc,CAAC,EAAE,MAAM,EACvB,WAAW,CAAC,EAAE,OAAO,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsDxB;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkBhE"}
|
|
@@ -3,15 +3,51 @@ import { pathExists } from './fs.utils.js';
|
|
|
3
3
|
import { LAYOUT_TEMPLATE } from '../../constants.js';
|
|
4
4
|
import { resolveSrcDir } from './paths.utils.js';
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Directory prefix index per page list, so a build computes it once instead of
|
|
7
|
+
* scanning every page for every page.
|
|
8
|
+
*
|
|
9
|
+
* The index is a snapshot of the page URLs taken at the first lookup for a given array,
|
|
10
|
+
* and every later lookup for that array reuses it. In a build the first lookup happens
|
|
11
|
+
* in the render loop, after the `beforeAll` hook, so URL changes made there are included.
|
|
12
|
+
* Changes made to the same array or its pages after that are not seen; a fresh build
|
|
13
|
+
* loads a new array and gets a new snapshot. Taking one snapshot keeps rendering and
|
|
14
|
+
* ISG dependency tracking in agreement for the whole build.
|
|
8
15
|
*/
|
|
16
|
+
const directoryPrefixCache = new WeakMap();
|
|
17
|
+
function getDirectoryPrefixIndex(allPages) {
|
|
18
|
+
const cached = directoryPrefixCache.get(allPages);
|
|
19
|
+
if (cached) {
|
|
20
|
+
return cached;
|
|
21
|
+
}
|
|
22
|
+
const index = new Map();
|
|
23
|
+
for (const { url } of allPages) {
|
|
24
|
+
// Every prefix ending at a '/' is a directory this URL lives in (or is, for trailing-slash URLs)
|
|
25
|
+
for (let i = url.indexOf('/'); i !== -1; i = url.indexOf('/', i + 1)) {
|
|
26
|
+
const prefix = url.slice(0, i + 1);
|
|
27
|
+
const existing = index.get(prefix);
|
|
28
|
+
if (existing === undefined) {
|
|
29
|
+
index.set(prefix, url);
|
|
30
|
+
}
|
|
31
|
+
else if (existing !== null && existing !== url) {
|
|
32
|
+
index.set(prefix, null);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
directoryPrefixCache.set(allPages, index);
|
|
37
|
+
return index;
|
|
38
|
+
}
|
|
9
39
|
/**
|
|
10
40
|
* Determines if the given page is a collection index page.
|
|
11
41
|
* A collection index page is one whose URL matches a directory path that contains other pages.
|
|
12
42
|
*
|
|
43
|
+
* Rendering and ISG dependency tracking must both call this with the same page list,
|
|
44
|
+
* otherwise they can pick different layouts for the same page.
|
|
45
|
+
*
|
|
46
|
+
* Page URLs are read once per `allPages` array, at the first call; see
|
|
47
|
+
* `directoryPrefixCache` for when changes to the pages are picked up.
|
|
48
|
+
*
|
|
13
49
|
* @param page - The page to check
|
|
14
|
-
* @param allPages - All pages in the site (
|
|
50
|
+
* @param allPages - All pages in the site (treated as unchanging after the first call)
|
|
15
51
|
* @returns True if the page is a collection index page
|
|
16
52
|
*/
|
|
17
53
|
export function isCollectionIndexPage(page, allPages) {
|
|
@@ -19,15 +55,10 @@ export function isCollectionIndexPage(page, allPages) {
|
|
|
19
55
|
if (page.url === '/') {
|
|
20
56
|
return true;
|
|
21
57
|
}
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return allPages.some((otherPage) => otherPage.url !== page.url && otherPage.url.startsWith(pageUrlAsDir));
|
|
27
|
-
}
|
|
28
|
-
// Simplified version when we don't have all pages
|
|
29
|
-
// Assume any page ending in /index or at root is a collection page
|
|
30
|
-
return page.url.endsWith('/index') || page.slug === 'index';
|
|
58
|
+
// The page is an index when a different page's URL lives under this page's URL as a directory
|
|
59
|
+
const pageUrlAsDir = page.url.endsWith('/') ? page.url : page.url + '/';
|
|
60
|
+
const containingUrl = getDirectoryPrefixIndex(allPages).get(pageUrlAsDir);
|
|
61
|
+
return containingUrl === null || (containingUrl !== undefined && containingUrl !== page.url);
|
|
31
62
|
}
|
|
32
63
|
/**
|
|
33
64
|
* Discovers layout files by searching up the directory hierarchy.
|