@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
package/dist/core/dev.js CHANGED
@@ -5,30 +5,15 @@ import { readFile, stat } from 'fs/promises';
5
5
  import { WebSocketServer } from 'ws';
6
6
  import chokidar from 'chokidar';
7
7
  import { build } from './build.js';
8
+ import { invalidate } from './invalidate.js';
8
9
  import { loadConfig } from '../config/loader.js';
9
10
  import { loadCacheManifest, saveCacheManifest } from './isg/manifest.js';
11
+ import { resolveDevPaths, resolveCacheDir } from './utils/paths.js';
12
+ import { DEFAULT_DEV_PORT, DEFAULT_DEV_HOST, TEMPLATE_EXTENSION } from '../constants.js';
10
13
  /**
11
- * Creates and configures a development server with live reload functionality.
12
- *
13
- * @param options - Development server configuration options
14
- * @returns Promise resolving to a DevServer instance
14
+ * Loads and validates configuration for the dev server.
15
15
  */
16
- export async function createDevServer(options = {}) {
17
- const { port = 3000, host = 'localhost', open = false, configPath, logger = {
18
- info: (msg) => console.log(msg),
19
- success: (msg) => console.log(msg),
20
- error: (msg) => console.error(msg),
21
- warning: (msg) => console.warn(msg),
22
- building: (msg) => console.log(msg),
23
- processing: (msg) => console.log(msg),
24
- stats: (msg) => console.log(msg),
25
- }, } = options;
26
- const url = `http://${host}:${port}`;
27
- let httpServer = null;
28
- let wsServer = null;
29
- let watcher = null;
30
- let config;
31
- let isBuilding = false;
16
+ async function loadDevConfig(configPath, logger) {
32
17
  // Load configuration
33
18
  try {
34
19
  if (configPath) {
@@ -36,141 +21,177 @@ export async function createDevServer(options = {}) {
36
21
  // This is a limitation of the current loadConfig implementation
37
22
  logger.info?.(`Loading config from: ${configPath}`);
38
23
  }
39
- config = await loadConfig(process.cwd());
24
+ const config = await loadConfig(process.cwd());
25
+ const { outDir, srcDir, staticDir } = resolveDevPaths(config);
26
+ return { config, outDir, srcDir, staticDir };
40
27
  }
41
28
  catch (error) {
42
29
  logger.error?.(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
43
30
  throw error;
44
31
  }
45
- const outDir = join(process.cwd(), config.outDir || 'dist');
46
- const srcDir = join(process.cwd(), config.srcDir || 'site');
47
- const staticDir = join(process.cwd(), config.staticDir || 'public');
48
- /**
49
- * Performs an initial build to ensure dist/ exists
50
- */
51
- async function initialBuild() {
52
- try {
32
+ }
33
+ /**
34
+ * Performs an initial build to ensure dist/ exists
35
+ */
36
+ async function performInitialBuild(configPath, logger) {
37
+ try {
38
+ // Clear cache to ensure fresh build on dev server start
39
+ logger.info?.('Clearing cache for fresh development build...');
40
+ await invalidate();
41
+ await build({
42
+ logger,
43
+ force: false,
44
+ clean: false,
45
+ ...(configPath && { configPath }),
46
+ });
47
+ }
48
+ catch (error) {
49
+ logger.error?.(`Initial build failed: ${error instanceof Error ? error.message : String(error)}`);
50
+ throw error;
51
+ }
52
+ }
53
+ /**
54
+ * Performs incremental rebuild when files change, using ISG logic for smart rebuilds
55
+ */
56
+ async function performIncrementalRebuild(changedPath, configPath, logger, wsServer, isBuildingRef) {
57
+ if (isBuildingRef.value) {
58
+ logger.info?.('Build in progress, skipping...');
59
+ return;
60
+ }
61
+ isBuildingRef.value = true;
62
+ const startTime = Date.now();
63
+ // Create a quiet logger for dev builds that suppresses verbose output
64
+ const devLogger = {
65
+ info: () => { }, // Suppress info messages
66
+ success: () => { }, // Suppress success messages
67
+ error: logger.error || (() => { }),
68
+ warning: logger.warning || (() => { }),
69
+ building: () => { }, // Suppress building messages
70
+ processing: () => { }, // Suppress processing messages
71
+ stats: () => { }, // Suppress stats messages
72
+ };
73
+ try {
74
+ const relativePath = changedPath
75
+ .replace(process.cwd(), '')
76
+ .replace(/\\/g, '/')
77
+ .replace(/^\//, '');
78
+ // Check if the changed file is a template/partial
79
+ if (changedPath.endsWith(TEMPLATE_EXTENSION) || changedPath.includes('_partials')) {
80
+ await handleTemplateChange(changedPath, configPath, devLogger);
81
+ }
82
+ else {
83
+ // Content or static file changed - use normal rebuild
53
84
  await build({
54
- logger,
85
+ logger: devLogger,
55
86
  force: false,
56
87
  clean: false,
57
88
  ...(configPath && { configPath }),
58
89
  });
59
90
  }
60
- catch (error) {
61
- logger.error?.(`Initial build failed: ${error instanceof Error ? error.message : String(error)}`);
62
- throw error;
91
+ // Notify all connected clients to reload
92
+ if (wsServer) {
93
+ wsServer.clients.forEach((client) => {
94
+ const ws = client;
95
+ if (ws.readyState === 1) {
96
+ // WebSocket.OPEN
97
+ ws.send(JSON.stringify({ type: 'reload' }));
98
+ }
99
+ });
63
100
  }
101
+ const duration = Date.now() - startTime;
102
+ logger.info?.(`⚡ ${relativePath} rebuilt in ${duration}ms`);
64
103
  }
65
- /**
66
- * Performs incremental rebuild when files change, using ISG logic for smart rebuilds
67
- */
68
- async function incrementalRebuild(changedPath) {
69
- if (isBuilding) {
70
- logger.info?.('⏳ Build in progress, skipping...');
104
+ catch (error) {
105
+ const duration = Date.now() - startTime;
106
+ logger.error?.(`❌ Rebuild failed after ${duration}ms: ${error instanceof Error ? error.message : String(error)}`);
107
+ }
108
+ finally {
109
+ isBuildingRef.value = false;
110
+ }
111
+ }
112
+ /**
113
+ * Handles template/partial file changes by invalidating affected pages
114
+ */
115
+ async function handleTemplateChange(templatePath, configPath, logger) {
116
+ const cacheDir = resolveCacheDir();
117
+ try {
118
+ // Load existing cache manifest
119
+ const cacheManifest = await loadCacheManifest(cacheDir);
120
+ if (!cacheManifest) {
121
+ // No cache exists, perform full rebuild
122
+ await build({
123
+ logger,
124
+ force: false,
125
+ clean: false,
126
+ ...(configPath && { configPath }),
127
+ });
71
128
  return;
72
129
  }
73
- isBuilding = true;
74
- const startTime = Date.now();
75
- // Create a quiet logger for dev builds that suppresses verbose output
76
- const devLogger = {
77
- info: () => { }, // Suppress info messages
78
- success: () => { }, // Suppress success messages
79
- error: logger.error || (() => { }),
80
- warning: logger.warning || (() => { }),
81
- building: () => { }, // Suppress building messages
82
- processing: () => { }, // Suppress processing messages
83
- stats: () => { }, // Suppress stats messages
84
- };
85
- try {
86
- const relativePath = changedPath
87
- .replace(process.cwd(), '')
88
- .replace(/\\/g, '/')
89
- .replace(/^\//, '');
90
- // Check if the changed file is a template/partial
91
- if (changedPath.endsWith('.eta') || changedPath.includes('_partials')) {
92
- await handleTemplateChange(changedPath, devLogger);
93
- }
94
- else {
95
- // Content or static file changed - use normal rebuild
96
- await build({
97
- logger: devLogger,
98
- force: false,
99
- clean: false,
100
- ...(configPath && { configPath }),
101
- });
102
- }
103
- // Notify all connected clients to reload
104
- if (wsServer) {
105
- wsServer.clients.forEach((client) => {
106
- const ws = client;
107
- if (ws.readyState === 1) {
108
- // WebSocket.OPEN
109
- ws.send(JSON.stringify({ type: 'reload' }));
110
- }
111
- });
112
- }
113
- const duration = Date.now() - startTime;
114
- logger.info?.(`⚡ ${relativePath} rebuilt in ${duration}ms`);
115
- }
116
- catch (error) {
117
- const duration = Date.now() - startTime;
118
- logger.error?.(`❌ Rebuild failed after ${duration}ms: ${error instanceof Error ? error.message : String(error)}`);
119
- }
120
- finally {
121
- isBuilding = false;
122
- }
123
- }
124
- /**
125
- * Handles template/partial file changes by invalidating affected pages
126
- */
127
- async function handleTemplateChange(templatePath, buildLogger) {
128
- const cacheDir = join(process.cwd(), '.stati');
129
- const effectiveLogger = buildLogger || logger;
130
- try {
131
- // Load existing cache manifest
132
- let cacheManifest = await loadCacheManifest(cacheDir);
133
- if (!cacheManifest) {
134
- // No cache exists, perform full rebuild
135
- await build({
136
- logger: effectiveLogger,
137
- force: false,
138
- clean: false,
139
- ...(configPath && { configPath }),
140
- });
141
- return;
142
- }
143
- // Find pages that depend on this template
144
- const affectedPages = [];
145
- for (const [pagePath, entry] of Object.entries(cacheManifest.entries)) {
146
- if (entry.deps.some((dep) => dep.includes(posix.normalize(templatePath.replace(/\\/g, '/'))))) {
147
- affectedPages.push(pagePath);
148
- // Remove from cache to force rebuild
149
- delete cacheManifest.entries[pagePath];
150
- }
151
- }
152
- if (affectedPages.length > 0) {
153
- // Save updated cache manifest
154
- await saveCacheManifest(cacheDir, cacheManifest);
155
- // Perform incremental rebuild (only affected pages will be rebuilt)
156
- await build({
157
- logger: effectiveLogger,
158
- force: false,
159
- clean: false,
160
- ...(configPath && { configPath }),
161
- });
130
+ // Find pages that depend on this template
131
+ const affectedPages = [];
132
+ const normalizedTemplatePath = posix.normalize(templatePath.replace(/\\/g, '/'));
133
+ for (const [pagePath, entry] of Object.entries(cacheManifest.entries)) {
134
+ if (entry.deps.some((dep) => {
135
+ const normalizedDep = posix.normalize(dep.replace(/\\/g, '/'));
136
+ // Use endsWith for more precise matching to avoid false positives
137
+ return (normalizedDep === normalizedTemplatePath ||
138
+ normalizedDep.endsWith('/' + normalizedTemplatePath));
139
+ })) {
140
+ affectedPages.push(pagePath);
141
+ // Remove from cache to force rebuild
142
+ delete cacheManifest.entries[pagePath];
162
143
  }
163
144
  }
164
- catch {
165
- // Fallback to full rebuild
145
+ if (affectedPages.length > 0) {
146
+ // Save updated cache manifest
147
+ await saveCacheManifest(cacheDir, cacheManifest);
148
+ // Perform incremental rebuild (only affected pages will be rebuilt)
166
149
  await build({
167
- logger: effectiveLogger,
150
+ logger,
168
151
  force: false,
169
152
  clean: false,
170
153
  ...(configPath && { configPath }),
171
154
  });
172
155
  }
156
+ else {
157
+ // If no affected pages were found but a template changed,
158
+ // force a full rebuild to ensure changes are reflected
159
+ // This can happen if dependency tracking missed something
160
+ await build({
161
+ logger,
162
+ force: true,
163
+ clean: false,
164
+ ...(configPath && { configPath }),
165
+ });
166
+ }
173
167
  }
168
+ catch {
169
+ // Fallback to full rebuild
170
+ await build({
171
+ logger,
172
+ force: false,
173
+ clean: false,
174
+ ...(configPath && { configPath }),
175
+ });
176
+ }
177
+ }
178
+ export async function createDevServer(options = {}) {
179
+ const { port = DEFAULT_DEV_PORT, host = DEFAULT_DEV_HOST, open = false, configPath, logger = {
180
+ info: (msg) => console.log(msg),
181
+ success: (msg) => console.log(msg),
182
+ error: (msg) => console.error(msg),
183
+ warning: (msg) => console.warn(msg),
184
+ building: (msg) => console.log(msg),
185
+ processing: (msg) => console.log(msg),
186
+ stats: (msg) => console.log(msg),
187
+ }, } = options;
188
+ const url = `http://${host}:${port}`;
189
+ let httpServer = null;
190
+ let wsServer = null;
191
+ let watcher = null;
192
+ const isBuildingRef = { value: false };
193
+ // Load configuration
194
+ const { config: _config, outDir, srcDir, staticDir } = await loadDevConfig(configPath, logger);
174
195
  /**
175
196
  * Gets MIME type for a file based on its extension
176
197
  */
@@ -243,11 +264,21 @@ export async function createDevServer(options = {}) {
243
264
  filePath = indexPath;
244
265
  }
245
266
  catch {
246
- return {
247
- content: '404 - Directory listing not available',
248
- mimeType: 'text/plain',
249
- statusCode: 404,
250
- };
267
+ // If no index.html in directory, try to serve corresponding .html file
268
+ // For example: /examples/ -> examples.html
269
+ const directoryName = requestPath.replace(/\/$/, ''); // Remove trailing slash
270
+ const fallbackPath = join(outDir, `${directoryName}.html`);
271
+ try {
272
+ await stat(fallbackPath);
273
+ filePath = fallbackPath;
274
+ }
275
+ catch {
276
+ return {
277
+ content: '404 - Directory listing not available',
278
+ mimeType: 'text/plain',
279
+ statusCode: 404,
280
+ };
281
+ }
251
282
  }
252
283
  }
253
284
  const mimeType = getMimeType(filePath);
@@ -269,6 +300,36 @@ export async function createDevServer(options = {}) {
269
300
  };
270
301
  }
271
302
  catch {
303
+ // File not found, try some fallback strategies for pretty URLs
304
+ if (requestPath.endsWith('/')) {
305
+ // For requests ending with /, try the corresponding .html file
306
+ const pathWithoutSlash = requestPath.slice(0, -1);
307
+ const htmlPath = join(outDir, `${pathWithoutSlash}.html`);
308
+ try {
309
+ const stats = await stat(htmlPath);
310
+ if (stats.isFile()) {
311
+ const mimeType = getMimeType(htmlPath);
312
+ const content = await readFile(htmlPath);
313
+ if (mimeType === 'text/html') {
314
+ const html = content.toString('utf-8');
315
+ const injectedHtml = injectLiveReloadScript(html);
316
+ return {
317
+ content: injectedHtml,
318
+ mimeType,
319
+ statusCode: 200,
320
+ };
321
+ }
322
+ return {
323
+ content,
324
+ mimeType,
325
+ statusCode: 200,
326
+ };
327
+ }
328
+ }
329
+ catch {
330
+ // Continue to 404
331
+ }
332
+ }
272
333
  // File not found
273
334
  return {
274
335
  content: '404 - File not found',
@@ -281,7 +342,7 @@ export async function createDevServer(options = {}) {
281
342
  url,
282
343
  async start() {
283
344
  // Perform initial build
284
- await initialBuild();
345
+ await performInitialBuild(configPath, logger);
285
346
  // Create HTTP server
286
347
  httpServer = createServer(async (req, res) => {
287
348
  const requestPath = req.url || '/';
@@ -311,7 +372,7 @@ export async function createDevServer(options = {}) {
311
372
  path: '/__ws',
312
373
  });
313
374
  wsServer.on('connection', (ws) => {
314
- logger.info?.('🔗 Browser connected for live reload');
375
+ logger.info?.('Browser connected for live reload');
315
376
  const websocket = ws;
316
377
  websocket.on('close', () => {
317
378
  logger.info?.('Browser disconnected from live reload');
@@ -334,13 +395,13 @@ export async function createDevServer(options = {}) {
334
395
  ignoreInitial: true,
335
396
  });
336
397
  watcher.on('change', (path) => {
337
- void incrementalRebuild(path);
398
+ void performIncrementalRebuild(path, configPath, logger, wsServer, isBuildingRef);
338
399
  });
339
400
  watcher.on('add', (path) => {
340
- void incrementalRebuild(path);
401
+ void performIncrementalRebuild(path, configPath, logger, wsServer, isBuildingRef);
341
402
  });
342
403
  watcher.on('unlink', (path) => {
343
- void incrementalRebuild(path);
404
+ void performIncrementalRebuild(path, configPath, logger, wsServer, isBuildingRef);
344
405
  });
345
406
  logger.success?.(`Dev server running at ${url}`);
346
407
  logger.info?.(`\nServing from:`);
@@ -1,4 +1,4 @@
1
- import type { CacheEntry } from '../types.js';
1
+ import type { CacheEntry } from '../types/index.js';
2
2
  /**
3
3
  * Invalidation result containing affected cache entries.
4
4
  */
@@ -1 +1 @@
1
- {"version":3,"file":"invalidate.d.ts","sourceRoot":"","sources":["../../src/core/invalidate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0CAA0C;IAC1C,gBAAgB,EAAE,MAAM,CAAC;IACzB,iCAAiC;IACjC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,2CAA2C;IAC3C,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAkC9D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAkC9F;AAiLD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAoD5E"}
1
+ {"version":3,"file":"invalidate.d.ts","sourceRoot":"","sources":["../../src/core/invalidate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0CAA0C;IAC1C,gBAAgB,EAAE,MAAM,CAAC;IACzB,iCAAiC;IACjC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,2CAA2C;IAC3C,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAkC9D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAkC9F;AAiLD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAoD5E"}
@@ -1,5 +1,5 @@
1
- import { join } from 'path';
2
1
  import { loadCacheManifest, saveCacheManifest } from './isg/manifest.js';
2
+ import { resolveCacheDir } from './utils/paths.js';
3
3
  /**
4
4
  * Parses an invalidation query string into individual query terms.
5
5
  * Supports space-separated values and quoted strings.
@@ -281,9 +281,9 @@ function matchesAge(entry, ageValue) {
281
281
  * ```
282
282
  */
283
283
  export async function invalidate(query) {
284
- const cacheDir = join(process.cwd(), '.stati');
284
+ const cacheDir = resolveCacheDir();
285
285
  // Load existing cache manifest
286
- let cacheManifest = await loadCacheManifest(cacheDir);
286
+ const cacheManifest = await loadCacheManifest(cacheDir);
287
287
  if (!cacheManifest) {
288
288
  // No cache to invalidate
289
289
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"build-lock.d.ts","sourceRoot":"","sources":["../../../src/core/isg/build-lock.ts"],"names":[],"mappings":"AAYA;;GAEG;AACH,UAAU,SAAS;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;gBAEb,QAAQ,EAAE,MAAM;IAI5B;;;;;;;;;;;;;;;;;;;OAmBG;IACG,WAAW,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAoDrF;;;;;;;OAOG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBlC;;;;;;;;;;;OAWG;IACG,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAiBpC;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAY9C;;;OAGG;YACW,eAAe;IAQ7B;;OAEG;YACW,cAAc;IAc5B;;OAEG;YACW,YAAY;IAS1B;;OAEG;YACW,gBAAgB;IAY9B;;OAEG;IACH,OAAO,CAAC,WAAW;IAQnB;;OAEG;IACH,OAAO,CAAC,KAAK;CAGd;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,OAAO,CAAC,CAAC,CAAC,CASZ"}
1
+ {"version":3,"file":"build-lock.d.ts","sourceRoot":"","sources":["../../../src/core/isg/build-lock.ts"],"names":[],"mappings":"AAWA;;GAEG;AACH,UAAU,SAAS;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;gBAEb,QAAQ,EAAE,MAAM;IAI5B;;;;;;;;;;;;;;;;;;;OAmBG;IACG,WAAW,CAAC,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAoDrF;;;;;;;OAOG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBlC;;;;;;;;;;;OAWG;IACG,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAiBpC;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAY9C;;;OAGG;YACW,eAAe;IAQ7B;;OAEG;YACW,cAAc;IAc5B;;OAEG;YACW,YAAY;IAY1B;;OAEG;YACW,gBAAgB;IAY9B;;OAEG;IACH,OAAO,CAAC,WAAW;IAQnB;;OAEG;IACH,OAAO,CAAC,KAAK;CAGd;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,OAAO,CAAC,CAAC,CAAC,CASZ"}
@@ -1,5 +1,4 @@
1
- import fse from 'fs-extra';
2
- const { writeFile, readFile, pathExists, remove, ensureDir } = fse;
1
+ import { writeFile, readFile, pathExists, remove, ensureDir } from '../utils/fs.js';
3
2
  import { join, dirname } from 'path';
4
3
  import { hostname } from 'os';
5
4
  /**
@@ -178,6 +177,9 @@ export class BuildLockManager {
178
177
  async readLockFile() {
179
178
  try {
180
179
  const content = await readFile(this.lockPath, 'utf-8');
180
+ if (!content) {
181
+ return null;
182
+ }
181
183
  return JSON.parse(content);
182
184
  }
183
185
  catch {
@@ -1,4 +1,4 @@
1
- import type { PageModel, CacheEntry, StatiConfig } from '../../types.js';
1
+ import type { PageModel, CacheEntry, StatiConfig } from '../../types/index.js';
2
2
  /**
3
3
  * Determines if a page should be rebuilt based on ISG logic.
4
4
  * Checks content changes, dependency changes, TTL expiration, and freeze status.
@@ -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,gBAAgB,CAAC;AAmEzE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,MAAM,EAAE,WAAW,EACnB,GAAG,EAAE,IAAI,GACR,OAAO,CAAC,OAAO,CAAC,CAgKlB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,IAAI,GACf,OAAO,CAAC,UAAU,CAAC,CA2ErB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,IAAI,GACf,OAAO,CAAC,UAAU,CAAC,CAUrB"}
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;AAmE/E;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,MAAM,EAAE,WAAW,EACnB,GAAG,EAAE,IAAI,GACR,OAAO,CAAC,OAAO,CAAC,CAgKlB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,IAAI,GACf,OAAO,CAAC,UAAU,CAAC,CA2ErB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,IAAI,GACf,OAAO,CAAC,UAAU,CAAC,CAUrB"}
@@ -1,4 +1,4 @@
1
- import type { PageModel, StatiConfig } from '../../types.js';
1
+ import type { PageModel, StatiConfig } from '../../types/index.js';
2
2
  /**
3
3
  * Error thrown when a circular dependency is detected in templates.
4
4
  */
@@ -1 +1 @@
1
- {"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../../src/core/isg/deps.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7D;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,eAAe,EAAE,MAAM,EAAE;gBAAzB,eAAe,EAAE,MAAM,EAAE,EACzC,OAAO,EAAE,MAAM;CAKlB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,EAAE,CAAC,CAoCnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,EAAE,CAAC,CAiDnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CASxB"}
1
+ {"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../../src/core/isg/deps.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAKnE;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,eAAe,EAAE,MAAM,EAAE;gBAAzB,eAAe,EAAE,MAAM,EAAE,EACzC,OAAO,EAAE,MAAM;CAKlB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,EAAE,CAAC,CAoCnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,EAAE,CAAC,CAiDnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CASxB"}