@stati/core 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/config/loader.d.ts +7 -1
  2. package/dist/config/loader.d.ts.map +1 -1
  3. package/dist/config/loader.js +17 -12
  4. package/dist/constants.d.ts +71 -0
  5. package/dist/constants.d.ts.map +1 -0
  6. package/dist/constants.js +78 -0
  7. package/dist/core/build.d.ts +1 -1
  8. package/dist/core/build.d.ts.map +1 -1
  9. package/dist/core/build.js +94 -69
  10. package/dist/core/content.d.ts +1 -1
  11. package/dist/core/content.d.ts.map +1 -1
  12. package/dist/core/content.js +10 -5
  13. package/dist/core/dev.d.ts +1 -7
  14. package/dist/core/dev.d.ts.map +1 -1
  15. package/dist/core/dev.js +202 -141
  16. package/dist/core/invalidate.d.ts +1 -1
  17. package/dist/core/invalidate.d.ts.map +1 -1
  18. package/dist/core/invalidate.js +3 -3
  19. package/dist/core/isg/build-lock.d.ts.map +1 -1
  20. package/dist/core/isg/build-lock.js +4 -2
  21. package/dist/core/isg/builder.d.ts +1 -1
  22. package/dist/core/isg/builder.d.ts.map +1 -1
  23. package/dist/core/isg/deps.d.ts +1 -1
  24. package/dist/core/isg/deps.d.ts.map +1 -1
  25. package/dist/core/isg/deps.js +59 -78
  26. package/dist/core/isg/hash.d.ts.map +1 -1
  27. package/dist/core/isg/hash.js +26 -17
  28. package/dist/core/isg/manifest.d.ts +1 -1
  29. package/dist/core/isg/manifest.d.ts.map +1 -1
  30. package/dist/core/isg/manifest.js +21 -8
  31. package/dist/core/isg/ttl.d.ts +1 -1
  32. package/dist/core/isg/ttl.d.ts.map +1 -1
  33. package/dist/core/isg/ttl.js +6 -9
  34. package/dist/core/isg/validation.d.ts +1 -1
  35. package/dist/core/isg/validation.d.ts.map +1 -1
  36. package/dist/core/markdown.d.ts +1 -1
  37. package/dist/core/markdown.d.ts.map +1 -1
  38. package/dist/core/navigation.d.ts +1 -1
  39. package/dist/core/navigation.d.ts.map +1 -1
  40. package/dist/core/preview.d.ts +19 -0
  41. package/dist/core/preview.d.ts.map +1 -0
  42. package/dist/core/preview.js +163 -0
  43. package/dist/core/templates.d.ts +1 -1
  44. package/dist/core/templates.d.ts.map +1 -1
  45. package/dist/core/templates.js +28 -105
  46. package/dist/core/utils/fs.d.ts +37 -0
  47. package/dist/core/utils/fs.d.ts.map +1 -0
  48. package/dist/core/utils/fs.js +86 -0
  49. package/dist/core/utils/partials.d.ts +24 -0
  50. package/dist/core/utils/partials.d.ts.map +1 -0
  51. package/dist/core/utils/partials.js +85 -0
  52. package/dist/core/utils/paths.d.ts +67 -0
  53. package/dist/core/utils/paths.d.ts.map +1 -0
  54. package/dist/core/utils/paths.js +86 -0
  55. package/dist/core/utils/template-discovery.d.ts +34 -0
  56. package/dist/core/utils/template-discovery.d.ts.map +1 -0
  57. package/dist/core/utils/template-discovery.js +111 -0
  58. package/dist/index.d.ts +4 -2
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1 -0
  61. package/dist/tests/utils/test-mocks.d.ts +69 -0
  62. package/dist/tests/utils/test-mocks.d.ts.map +1 -0
  63. package/dist/tests/utils/test-mocks.js +125 -0
  64. package/dist/types/config.d.ts +178 -0
  65. package/dist/types/config.d.ts.map +1 -0
  66. package/dist/types/config.js +1 -0
  67. package/dist/types/content.d.ts +124 -0
  68. package/dist/types/content.d.ts.map +1 -0
  69. package/dist/types/content.js +4 -0
  70. package/dist/types/index.d.ts +10 -0
  71. package/dist/types/index.d.ts.map +1 -0
  72. package/dist/types/index.js +5 -0
  73. package/dist/types/isg.d.ts +103 -0
  74. package/dist/types/isg.d.ts.map +1 -0
  75. package/dist/types/isg.js +4 -0
  76. package/dist/types/logging.d.ts +113 -0
  77. package/dist/types/logging.d.ts.map +1 -0
  78. package/dist/types/logging.js +4 -0
  79. package/dist/types/navigation.d.ts +43 -0
  80. package/dist/types/navigation.d.ts.map +1 -0
  81. package/dist/types/navigation.js +4 -0
  82. package/dist/types.d.ts +10 -10
  83. package/dist/types.d.ts.map +1 -1
  84. package/package.json +1 -1
@@ -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;AAM1B,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAkB,MAAM,aAAa,CAAC;AAgRnF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,GAAG,CAS7D;AAED,wBAAsB,UAAU,CAC9B,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EACnB,GAAG,EAAE,GAAG,EACR,UAAU,CAAC,EAAE,OAAO,EAAE,EACtB,QAAQ,CAAC,EAAE,SAAS,EAAE,GACrB,OAAO,CAAC,MAAM,CAAC,CAkEjB"}
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,CAU7D;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"}
@@ -1,43 +1,9 @@
1
1
  import { Eta } from 'eta';
2
- import { join, dirname, relative, basename } from 'path';
3
- import { posix } from 'path';
4
- import fse from 'fs-extra';
5
- const { pathExists } = fse;
2
+ import { join, dirname, relative, basename, posix } from 'path';
6
3
  import glob from 'fast-glob';
7
- /**
8
- * Determines if the given page is an index page for a collection.
9
- * An index page is one whose URL matches a directory path that contains other pages.
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,105 +108,62 @@ 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 = join(process.cwd(), config.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
- const dirsToScan = [''];
152
- for (let i = 0; i < pathSegments.length; i++) {
153
- dirsToScan.push(pathSegments.slice(0, i + 1).join('/'));
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 underscoreFolders = await glob('_*/', {
159
- cwd: searchDir,
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 folder of underscoreFolders) {
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, '.eta');
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
- partials[partialName] = relativePath;
149
+ partials[partialName] = posix.normalize(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 = join(process.cwd(), config.srcDir);
156
+ const templateDir = resolveSrcDir(config);
235
157
  const eta = new Eta({
236
158
  views: templateDir,
237
159
  cache: process.env.NODE_ENV === 'production',
160
+ cacheFilepaths: process.env.NODE_ENV === 'production',
238
161
  });
239
162
  return eta;
240
163
  }
241
164
  export async function renderPage(page, body, config, eta, navigation, allPages) {
242
165
  // Discover partials for this page's directory hierarchy
243
- const srcDir = join(process.cwd(), config.srcDir);
166
+ const srcDir = resolveSrcDir(config);
244
167
  const relativePath = relative(srcDir, page.sourcePath);
245
168
  const partialPaths = await discoverPartials(relativePath, config);
246
169
  // 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"}
@@ -0,0 +1,85 @@
1
+ // dedup: partials scanning centralized in utils/partials.ts
2
+ import { join, dirname, relative, basename, posix } from 'path';
3
+ import glob from 'fast-glob';
4
+ import { resolveSrcDir } from './paths.js';
5
+ /**
6
+ * Scan underscore folders from root to the page's directory for .eta partials.
7
+ * Returns structured entries usable by both renderer and ISG deps.
8
+ */
9
+ export async function scanPartialsHierarchy(pageRelativePath, config) {
10
+ const srcDir = resolveSrcDir(config);
11
+ const entries = [];
12
+ const dirsToScan = buildDirsToScan(pageRelativePath);
13
+ for (const dir of dirsToScan) {
14
+ const searchDir = dir ? join(srcDir, dir) : srcDir;
15
+ const normalizedSearchDir = searchDir.replace(/\\/g, '/');
16
+ const pattern = posix.join(normalizedSearchDir, '_*/**/*.eta');
17
+ try {
18
+ const files = (await glob(pattern, { absolute: true }));
19
+ for (const file of files) {
20
+ entries.push({
21
+ name: basename(file, '.eta'),
22
+ absPath: file,
23
+ relFromSrc: relative(srcDir, file),
24
+ });
25
+ }
26
+ }
27
+ catch {
28
+ // Ignore errors for non-existent directories, continue scanning
29
+ continue;
30
+ }
31
+ }
32
+ return entries;
33
+ }
34
+ /**
35
+ * Build the list of directories to scan for partials from root → current page directory.
36
+ */
37
+ export function buildDirsToScan(pageRelativePath) {
38
+ const pageDir = dirname(pageRelativePath);
39
+ const pathSegments = pageDir === '.' ? [] : pageDir.split(/[/\\]/);
40
+ const dirsToScan = [''];
41
+ for (let i = 0; i < pathSegments.length; i++) {
42
+ dirsToScan.push(pathSegments.slice(0, i + 1).join('/'));
43
+ }
44
+ return dirsToScan;
45
+ }
46
+ /**
47
+ * Renderer-focused partial discovery.
48
+ * Matches historical renderer behavior used by templates tests:
49
+ * - Only scans the current page directory (not parents)
50
+ * - First finds underscore folders (e.g. "_partials/")
51
+ * - Then finds .eta files within those folders, returning relative paths like "_partials/header.eta"
52
+ */
53
+ export async function scanPartialsForRenderer(pageRelativePath, config) {
54
+ const srcDir = resolveSrcDir(config);
55
+ const pageDirRel = dirname(pageRelativePath);
56
+ const searchDir = pageDirRel === '.' ? srcDir : join(srcDir, pageDirRel);
57
+ const entries = [];
58
+ try {
59
+ // Find underscore directories relative to the page directory
60
+ const underscoreDirs = (await glob('_*/', { cwd: searchDir }));
61
+ for (const dir of underscoreDirs) {
62
+ try {
63
+ // Find .eta files inside each underscore directory
64
+ const etaFiles = (await glob('*.eta', { cwd: join(searchDir, dir) }));
65
+ for (const file of etaFiles) {
66
+ const rel = join(dir, file);
67
+ entries.push({
68
+ name: basename(file, '.eta'),
69
+ absPath: join(searchDir, rel),
70
+ relFromSrc: rel,
71
+ });
72
+ }
73
+ }
74
+ catch {
75
+ // Ignore per-dir errors and continue
76
+ continue;
77
+ }
78
+ }
79
+ }
80
+ catch {
81
+ // Ignore errors and return empty list (graceful)
82
+ return [];
83
+ }
84
+ return entries;
85
+ }
@@ -0,0 +1,67 @@
1
+ import type { StatiConfig } from '../../types/index.js';
2
+ /**
3
+ * File system path resolution utilities for Stati core.
4
+ * Centralized utilities to ensure consistent path handling across the codebase.
5
+ */
6
+ /**
7
+ * Resolves the absolute source directory path from configuration.
8
+ * @param config - Stati configuration
9
+ * @returns Absolute path to the source directory
10
+ */
11
+ export declare function resolveSrcDir(config: StatiConfig): string;
12
+ /**
13
+ * Resolves the absolute output directory path from configuration.
14
+ * @param config - Stati configuration
15
+ * @returns Absolute path to the output directory
16
+ */
17
+ export declare function resolveOutDir(config: StatiConfig): string;
18
+ /**
19
+ * Resolves the absolute static directory path from configuration.
20
+ * @param config - Stati configuration
21
+ * @returns Absolute path to the static directory
22
+ */
23
+ export declare function resolveStaticDir(config: StatiConfig): string;
24
+ /**
25
+ * Resolves the absolute cache directory path.
26
+ * @returns Absolute path to the .stati cache directory
27
+ */
28
+ export declare function resolveCacheDir(): string;
29
+ /**
30
+ * Resolves paths with defaults for development mode.
31
+ * @param config - Stati configuration
32
+ * @returns Object with all resolved directory paths with sensible defaults
33
+ */
34
+ export declare function resolveDevPaths(config: StatiConfig): {
35
+ srcDir: string;
36
+ outDir: string;
37
+ staticDir: string;
38
+ };
39
+ /**
40
+ * Normalizes a template path for cross-platform compatibility.
41
+ * Ensures forward slashes are used for template engine compatibility.
42
+ * @param templatePath - Path to normalize
43
+ * @returns Normalized path with forward slashes
44
+ */
45
+ export declare function normalizeTemplatePath(templatePath: string): string;
46
+ /**
47
+ * Resolves a file path relative to the source directory.
48
+ * @param config - Stati configuration
49
+ * @param relativePath - Path relative to srcDir
50
+ * @returns Absolute path
51
+ */
52
+ export declare function resolveSrcPath(config: StatiConfig, relativePath: string): string;
53
+ /**
54
+ * Resolves a file path relative to the output directory.
55
+ * @param config - Stati configuration
56
+ * @param relativePath - Path relative to outDir
57
+ * @returns Absolute path
58
+ */
59
+ export declare function resolveOutPath(config: StatiConfig, relativePath: string): string;
60
+ /**
61
+ * Resolves a file path relative to the static directory.
62
+ * @param config - Stati configuration
63
+ * @param relativePath - Path relative to staticDir
64
+ * @returns Absolute path
65
+ */
66
+ export declare function resolveStaticPath(config: StatiConfig, relativePath: string): string;
67
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../../src/core/utils/paths.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAQxD;;;GAGG;AAEH;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAE5D;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW;;;;EAMlD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAEhF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAEnF"}
@@ -0,0 +1,86 @@
1
+ import { join } from 'path';
2
+ import { posix } from 'path';
3
+ import { DEFAULT_SRC_DIR, DEFAULT_OUT_DIR, DEFAULT_STATIC_DIR, CACHE_DIR_NAME, } from '../../constants.js';
4
+ /**
5
+ * File system path resolution utilities for Stati core.
6
+ * Centralized utilities to ensure consistent path handling across the codebase.
7
+ */
8
+ /**
9
+ * Resolves the absolute source directory path from configuration.
10
+ * @param config - Stati configuration
11
+ * @returns Absolute path to the source directory
12
+ */
13
+ export function resolveSrcDir(config) {
14
+ return join(process.cwd(), config.srcDir);
15
+ }
16
+ /**
17
+ * Resolves the absolute output directory path from configuration.
18
+ * @param config - Stati configuration
19
+ * @returns Absolute path to the output directory
20
+ */
21
+ export function resolveOutDir(config) {
22
+ return join(process.cwd(), config.outDir);
23
+ }
24
+ /**
25
+ * Resolves the absolute static directory path from configuration.
26
+ * @param config - Stati configuration
27
+ * @returns Absolute path to the static directory
28
+ */
29
+ export function resolveStaticDir(config) {
30
+ return join(process.cwd(), config.staticDir);
31
+ }
32
+ /**
33
+ * Resolves the absolute cache directory path.
34
+ * @returns Absolute path to the .stati cache directory
35
+ */
36
+ export function resolveCacheDir() {
37
+ return join(process.cwd(), CACHE_DIR_NAME);
38
+ }
39
+ /**
40
+ * Resolves paths with defaults for development mode.
41
+ * @param config - Stati configuration
42
+ * @returns Object with all resolved directory paths with sensible defaults
43
+ */
44
+ export function resolveDevPaths(config) {
45
+ return {
46
+ srcDir: join(process.cwd(), config.srcDir || DEFAULT_SRC_DIR),
47
+ outDir: join(process.cwd(), config.outDir || DEFAULT_OUT_DIR),
48
+ staticDir: join(process.cwd(), config.staticDir || DEFAULT_STATIC_DIR),
49
+ };
50
+ }
51
+ /**
52
+ * Normalizes a template path for cross-platform compatibility.
53
+ * Ensures forward slashes are used for template engine compatibility.
54
+ * @param templatePath - Path to normalize
55
+ * @returns Normalized path with forward slashes
56
+ */
57
+ export function normalizeTemplatePath(templatePath) {
58
+ return posix.normalize(templatePath.replace(/\\/g, '/'));
59
+ }
60
+ /**
61
+ * Resolves a file path relative to the source directory.
62
+ * @param config - Stati configuration
63
+ * @param relativePath - Path relative to srcDir
64
+ * @returns Absolute path
65
+ */
66
+ export function resolveSrcPath(config, relativePath) {
67
+ return join(resolveSrcDir(config), relativePath);
68
+ }
69
+ /**
70
+ * Resolves a file path relative to the output directory.
71
+ * @param config - Stati configuration
72
+ * @param relativePath - Path relative to outDir
73
+ * @returns Absolute path
74
+ */
75
+ export function resolveOutPath(config, relativePath) {
76
+ return join(resolveOutDir(config), relativePath);
77
+ }
78
+ /**
79
+ * Resolves a file path relative to the static directory.
80
+ * @param config - Stati configuration
81
+ * @param relativePath - Path relative to staticDir
82
+ * @returns Absolute path
83
+ */
84
+ export function resolveStaticPath(config, relativePath) {
85
+ return join(resolveStaticDir(config), relativePath);
86
+ }
@@ -0,0 +1,34 @@
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
+ /**
7
+ * Determines if the given page is a collection index page.
8
+ * A collection index page is one whose URL matches a directory path that contains other pages.
9
+ *
10
+ * @param page - The page to check
11
+ * @param allPages - All pages in the site (optional for simplified check)
12
+ * @returns True if the page is a collection index page
13
+ */
14
+ export declare function isCollectionIndexPage(page: PageModel, allPages?: PageModel[]): boolean;
15
+ /**
16
+ * Discovers layout files by searching up the directory hierarchy.
17
+ * Supports both explicit layout specification and automatic discovery.
18
+ *
19
+ * @param pagePath - The path to the page (relative to srcDir)
20
+ * @param config - Stati configuration
21
+ * @param explicitLayout - Explicit layout name from front-matter
22
+ * @param isIndexPage - Whether this is an aggregation/index page (enables index.eta lookup)
23
+ * @returns The layout file path (relative to srcDir) or null if none found
24
+ */
25
+ export declare function discoverLayout(pagePath: string, config: StatiConfig, explicitLayout?: string, isIndexPage?: boolean): Promise<string | null>;
26
+ /**
27
+ * Gets the collection path for a given page URL.
28
+ * For index pages, returns the page's URL. For child pages, returns the parent directory.
29
+ *
30
+ * @param pageUrl - The page URL
31
+ * @returns The collection path
32
+ */
33
+ export declare function getCollectionPathForPage(pageUrl: string): string;
34
+ //# sourceMappingURL=template-discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-discovery.d.ts","sourceRoot":"","sources":["../../../src/core/utils/template-discovery.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAInE;;;GAGG;AAEH;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,OAAO,CAkBtF;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"}