@stati/core 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/loader.d.ts +7 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +17 -12
- package/dist/constants.d.ts +71 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +78 -0
- package/dist/core/build.d.ts +1 -1
- package/dist/core/build.d.ts.map +1 -1
- package/dist/core/build.js +94 -69
- package/dist/core/content.d.ts +1 -1
- package/dist/core/content.d.ts.map +1 -1
- package/dist/core/content.js +10 -5
- package/dist/core/dev.d.ts +1 -7
- package/dist/core/dev.d.ts.map +1 -1
- package/dist/core/dev.js +135 -126
- package/dist/core/invalidate.d.ts +1 -1
- package/dist/core/invalidate.d.ts.map +1 -1
- package/dist/core/invalidate.js +3 -3
- package/dist/core/isg/build-lock.d.ts.map +1 -1
- package/dist/core/isg/build-lock.js +4 -2
- package/dist/core/isg/builder.d.ts +1 -1
- package/dist/core/isg/builder.d.ts.map +1 -1
- package/dist/core/isg/deps.d.ts +1 -1
- package/dist/core/isg/deps.d.ts.map +1 -1
- package/dist/core/isg/deps.js +59 -78
- package/dist/core/isg/hash.d.ts.map +1 -1
- package/dist/core/isg/hash.js +26 -17
- package/dist/core/isg/manifest.d.ts +1 -1
- package/dist/core/isg/manifest.d.ts.map +1 -1
- package/dist/core/isg/manifest.js +21 -8
- package/dist/core/isg/ttl.d.ts +1 -1
- package/dist/core/isg/ttl.d.ts.map +1 -1
- package/dist/core/isg/ttl.js +6 -9
- package/dist/core/isg/validation.d.ts +1 -1
- package/dist/core/isg/validation.d.ts.map +1 -1
- package/dist/core/markdown.d.ts +1 -1
- package/dist/core/markdown.d.ts.map +1 -1
- package/dist/core/navigation.d.ts +1 -1
- package/dist/core/navigation.d.ts.map +1 -1
- package/dist/core/templates.d.ts +1 -1
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +25 -103
- package/dist/core/utils/fs.d.ts +37 -0
- package/dist/core/utils/fs.d.ts.map +1 -0
- package/dist/core/utils/fs.js +86 -0
- package/dist/core/utils/partials.d.ts +24 -0
- package/dist/core/utils/partials.d.ts.map +1 -0
- package/dist/core/utils/partials.js +85 -0
- package/dist/core/utils/paths.d.ts +67 -0
- package/dist/core/utils/paths.d.ts.map +1 -0
- package/dist/core/utils/paths.js +86 -0
- package/dist/core/utils/template-discovery.d.ts +34 -0
- package/dist/core/utils/template-discovery.d.ts.map +1 -0
- package/dist/core/utils/template-discovery.js +111 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/tests/utils/test-mocks.d.ts +69 -0
- package/dist/tests/utils/test-mocks.d.ts.map +1 -0
- package/dist/tests/utils/test-mocks.js +125 -0
- package/dist/types/config.d.ts +178 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +1 -0
- package/dist/types/content.d.ts +124 -0
- package/dist/types/content.d.ts.map +1 -0
- package/dist/types/content.js +4 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/isg.d.ts +103 -0
- package/dist/types/isg.d.ts.map +1 -0
- package/dist/types/isg.js +4 -0
- package/dist/types/logging.d.ts +113 -0
- package/dist/types/logging.d.ts.map +1 -0
- package/dist/types/logging.js +4 -0
- package/dist/types/navigation.d.ts +43 -0
- package/dist/types/navigation.d.ts.map +1 -0
- package/dist/types/navigation.js +4 -0
- package/dist/types.d.ts +10 -10
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/core/dev.js
CHANGED
|
@@ -7,28 +7,12 @@ import chokidar from 'chokidar';
|
|
|
7
7
|
import { build } from './build.js';
|
|
8
8
|
import { loadConfig } from '../config/loader.js';
|
|
9
9
|
import { loadCacheManifest, saveCacheManifest } from './isg/manifest.js';
|
|
10
|
+
import { resolveDevPaths, resolveCacheDir } from './utils/paths.js';
|
|
11
|
+
import { DEFAULT_DEV_PORT, DEFAULT_DEV_HOST, TEMPLATE_EXTENSION } from '../constants.js';
|
|
10
12
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* @param options - Development server configuration options
|
|
14
|
-
* @returns Promise resolving to a DevServer instance
|
|
13
|
+
* Loads and validates configuration for the dev server.
|
|
15
14
|
*/
|
|
16
|
-
|
|
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;
|
|
15
|
+
async function loadDevConfig(configPath, logger) {
|
|
32
16
|
// Load configuration
|
|
33
17
|
try {
|
|
34
18
|
if (configPath) {
|
|
@@ -36,124 +20,122 @@ export async function createDevServer(options = {}) {
|
|
|
36
20
|
// This is a limitation of the current loadConfig implementation
|
|
37
21
|
logger.info?.(`Loading config from: ${configPath}`);
|
|
38
22
|
}
|
|
39
|
-
config = await loadConfig(process.cwd());
|
|
23
|
+
const config = await loadConfig(process.cwd());
|
|
24
|
+
const { outDir, srcDir, staticDir } = resolveDevPaths(config);
|
|
25
|
+
return { config, outDir, srcDir, staticDir };
|
|
40
26
|
}
|
|
41
27
|
catch (error) {
|
|
42
28
|
logger.error?.(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
|
|
43
29
|
throw error;
|
|
44
30
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Performs an initial build to ensure dist/ exists
|
|
34
|
+
*/
|
|
35
|
+
async function performInitialBuild(configPath, logger) {
|
|
36
|
+
try {
|
|
37
|
+
await build({
|
|
38
|
+
logger,
|
|
39
|
+
force: false,
|
|
40
|
+
clean: false,
|
|
41
|
+
...(configPath && { configPath }),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
logger.error?.(`Initial build failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Performs incremental rebuild when files change, using ISG logic for smart rebuilds
|
|
51
|
+
*/
|
|
52
|
+
async function performIncrementalRebuild(changedPath, configPath, logger, wsServer, isBuildingRef) {
|
|
53
|
+
if (isBuildingRef.value) {
|
|
54
|
+
logger.info?.('Build in progress, skipping...');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
isBuildingRef.value = true;
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
// Create a quiet logger for dev builds that suppresses verbose output
|
|
60
|
+
const devLogger = {
|
|
61
|
+
info: () => { }, // Suppress info messages
|
|
62
|
+
success: () => { }, // Suppress success messages
|
|
63
|
+
error: logger.error || (() => { }),
|
|
64
|
+
warning: logger.warning || (() => { }),
|
|
65
|
+
building: () => { }, // Suppress building messages
|
|
66
|
+
processing: () => { }, // Suppress processing messages
|
|
67
|
+
stats: () => { }, // Suppress stats messages
|
|
68
|
+
};
|
|
69
|
+
try {
|
|
70
|
+
const relativePath = changedPath
|
|
71
|
+
.replace(process.cwd(), '')
|
|
72
|
+
.replace(/\\/g, '/')
|
|
73
|
+
.replace(/^\//, '');
|
|
74
|
+
// Check if the changed file is a template/partial
|
|
75
|
+
if (changedPath.endsWith(TEMPLATE_EXTENSION) || changedPath.includes('_partials')) {
|
|
76
|
+
await handleTemplateChange(changedPath, configPath, devLogger);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Content or static file changed - use normal rebuild
|
|
53
80
|
await build({
|
|
54
|
-
logger,
|
|
81
|
+
logger: devLogger,
|
|
55
82
|
force: false,
|
|
56
83
|
clean: false,
|
|
57
84
|
...(configPath && { configPath }),
|
|
58
85
|
});
|
|
59
86
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
87
|
+
// Notify all connected clients to reload
|
|
88
|
+
if (wsServer) {
|
|
89
|
+
wsServer.clients.forEach((client) => {
|
|
90
|
+
const ws = client;
|
|
91
|
+
if (ws.readyState === 1) {
|
|
92
|
+
// WebSocket.OPEN
|
|
93
|
+
ws.send(JSON.stringify({ type: 'reload' }));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
63
96
|
}
|
|
97
|
+
const duration = Date.now() - startTime;
|
|
98
|
+
logger.info?.(`⚡ ${relativePath} rebuilt in ${duration}ms`);
|
|
64
99
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
100
|
+
catch (error) {
|
|
101
|
+
const duration = Date.now() - startTime;
|
|
102
|
+
logger.error?.(`❌ Rebuild failed after ${duration}ms: ${error instanceof Error ? error.message : String(error)}`);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
isBuildingRef.value = false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Handles template/partial file changes by invalidating affected pages
|
|
110
|
+
*/
|
|
111
|
+
async function handleTemplateChange(templatePath, configPath, logger) {
|
|
112
|
+
const cacheDir = resolveCacheDir();
|
|
113
|
+
try {
|
|
114
|
+
// Load existing cache manifest
|
|
115
|
+
const cacheManifest = await loadCacheManifest(cacheDir);
|
|
116
|
+
if (!cacheManifest) {
|
|
117
|
+
// No cache exists, perform full rebuild
|
|
118
|
+
await build({
|
|
119
|
+
logger,
|
|
120
|
+
force: false,
|
|
121
|
+
clean: false,
|
|
122
|
+
...(configPath && { configPath }),
|
|
123
|
+
});
|
|
71
124
|
return;
|
|
72
125
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
else {
|
|
81
|
-
// Content or static file changed - use normal rebuild
|
|
82
|
-
logger.info?.(`📄 Content changed: ${changedPath}`);
|
|
83
|
-
await build({
|
|
84
|
-
logger,
|
|
85
|
-
force: false,
|
|
86
|
-
clean: false,
|
|
87
|
-
...(configPath && { configPath }),
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
// Notify all connected clients to reload
|
|
91
|
-
if (wsServer) {
|
|
92
|
-
wsServer.clients.forEach((client) => {
|
|
93
|
-
const ws = client;
|
|
94
|
-
if (ws.readyState === 1) {
|
|
95
|
-
// WebSocket.OPEN
|
|
96
|
-
ws.send(JSON.stringify({ type: 'reload' }));
|
|
97
|
-
}
|
|
98
|
-
});
|
|
126
|
+
// Find pages that depend on this template
|
|
127
|
+
const affectedPages = [];
|
|
128
|
+
for (const [pagePath, entry] of Object.entries(cacheManifest.entries)) {
|
|
129
|
+
if (entry.deps.some((dep) => dep.includes(posix.normalize(templatePath.replace(/\\/g, '/'))))) {
|
|
130
|
+
affectedPages.push(pagePath);
|
|
131
|
+
// Remove from cache to force rebuild
|
|
132
|
+
delete cacheManifest.entries[pagePath];
|
|
99
133
|
}
|
|
100
|
-
logger.success?.('Rebuild complete');
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
logger.error?.(`Rebuild failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
104
134
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Handles template/partial file changes by invalidating affected pages
|
|
111
|
-
*/
|
|
112
|
-
async function handleTemplateChange(templatePath) {
|
|
113
|
-
const cacheDir = join(process.cwd(), '.stati');
|
|
114
|
-
try {
|
|
115
|
-
// Load existing cache manifest
|
|
116
|
-
let cacheManifest = await loadCacheManifest(cacheDir);
|
|
117
|
-
if (!cacheManifest) {
|
|
118
|
-
// No cache exists, perform full rebuild
|
|
119
|
-
logger.info?.('No cache found, performing full rebuild...');
|
|
120
|
-
await build({
|
|
121
|
-
logger,
|
|
122
|
-
force: false,
|
|
123
|
-
clean: false,
|
|
124
|
-
...(configPath && { configPath }),
|
|
125
|
-
});
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
// Find pages that depend on this template
|
|
129
|
-
const affectedPages = [];
|
|
130
|
-
for (const [pagePath, entry] of Object.entries(cacheManifest.entries)) {
|
|
131
|
-
if (entry.deps.some((dep) => dep.includes(posix.normalize(templatePath.replace(/\\/g, '/'))))) {
|
|
132
|
-
affectedPages.push(pagePath);
|
|
133
|
-
// Remove from cache to force rebuild
|
|
134
|
-
delete cacheManifest.entries[pagePath];
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (affectedPages.length > 0) {
|
|
138
|
-
logger.info?.(`🎯 Invalidating ${affectedPages.length} affected pages:`);
|
|
139
|
-
affectedPages.forEach((page) => logger.info?.(` 📄 ${page}`));
|
|
140
|
-
// Save updated cache manifest
|
|
141
|
-
await saveCacheManifest(cacheDir, cacheManifest);
|
|
142
|
-
// Perform incremental rebuild (only affected pages will be rebuilt)
|
|
143
|
-
await build({
|
|
144
|
-
logger,
|
|
145
|
-
force: false,
|
|
146
|
-
clean: false,
|
|
147
|
-
...(configPath && { configPath }),
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
logger.info?.('ℹ️ No pages affected by template change');
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
catch (error) {
|
|
155
|
-
logger.warning?.(`Template dependency analysis failed, performing full rebuild: ${error instanceof Error ? error.message : String(error)}`);
|
|
156
|
-
// Fallback to full rebuild
|
|
135
|
+
if (affectedPages.length > 0) {
|
|
136
|
+
// Save updated cache manifest
|
|
137
|
+
await saveCacheManifest(cacheDir, cacheManifest);
|
|
138
|
+
// Perform incremental rebuild (only affected pages will be rebuilt)
|
|
157
139
|
await build({
|
|
158
140
|
logger,
|
|
159
141
|
force: false,
|
|
@@ -162,6 +144,33 @@ export async function createDevServer(options = {}) {
|
|
|
162
144
|
});
|
|
163
145
|
}
|
|
164
146
|
}
|
|
147
|
+
catch {
|
|
148
|
+
// Fallback to full rebuild
|
|
149
|
+
await build({
|
|
150
|
+
logger,
|
|
151
|
+
force: false,
|
|
152
|
+
clean: false,
|
|
153
|
+
...(configPath && { configPath }),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export async function createDevServer(options = {}) {
|
|
158
|
+
const { port = DEFAULT_DEV_PORT, host = DEFAULT_DEV_HOST, open = false, configPath, logger = {
|
|
159
|
+
info: (msg) => console.log(msg),
|
|
160
|
+
success: (msg) => console.log(msg),
|
|
161
|
+
error: (msg) => console.error(msg),
|
|
162
|
+
warning: (msg) => console.warn(msg),
|
|
163
|
+
building: (msg) => console.log(msg),
|
|
164
|
+
processing: (msg) => console.log(msg),
|
|
165
|
+
stats: (msg) => console.log(msg),
|
|
166
|
+
}, } = options;
|
|
167
|
+
const url = `http://${host}:${port}`;
|
|
168
|
+
let httpServer = null;
|
|
169
|
+
let wsServer = null;
|
|
170
|
+
let watcher = null;
|
|
171
|
+
const isBuildingRef = { value: false };
|
|
172
|
+
// Load configuration
|
|
173
|
+
const { config: _config, outDir, srcDir, staticDir } = await loadDevConfig(configPath, logger);
|
|
165
174
|
/**
|
|
166
175
|
* Gets MIME type for a file based on its extension
|
|
167
176
|
*/
|
|
@@ -272,7 +281,7 @@ export async function createDevServer(options = {}) {
|
|
|
272
281
|
url,
|
|
273
282
|
async start() {
|
|
274
283
|
// Perform initial build
|
|
275
|
-
await
|
|
284
|
+
await performInitialBuild(configPath, logger);
|
|
276
285
|
// Create HTTP server
|
|
277
286
|
httpServer = createServer(async (req, res) => {
|
|
278
287
|
const requestPath = req.url || '/';
|
|
@@ -302,7 +311,7 @@ export async function createDevServer(options = {}) {
|
|
|
302
311
|
path: '/__ws',
|
|
303
312
|
});
|
|
304
313
|
wsServer.on('connection', (ws) => {
|
|
305
|
-
logger.info?.('
|
|
314
|
+
logger.info?.('Browser connected for live reload');
|
|
306
315
|
const websocket = ws;
|
|
307
316
|
websocket.on('close', () => {
|
|
308
317
|
logger.info?.('Browser disconnected from live reload');
|
|
@@ -325,13 +334,13 @@ export async function createDevServer(options = {}) {
|
|
|
325
334
|
ignoreInitial: true,
|
|
326
335
|
});
|
|
327
336
|
watcher.on('change', (path) => {
|
|
328
|
-
void
|
|
337
|
+
void performIncrementalRebuild(path, configPath, logger, wsServer, isBuildingRef);
|
|
329
338
|
});
|
|
330
339
|
watcher.on('add', (path) => {
|
|
331
|
-
void
|
|
340
|
+
void performIncrementalRebuild(path, configPath, logger, wsServer, isBuildingRef);
|
|
332
341
|
});
|
|
333
342
|
watcher.on('unlink', (path) => {
|
|
334
|
-
void
|
|
343
|
+
void performIncrementalRebuild(path, configPath, logger, wsServer, isBuildingRef);
|
|
335
344
|
});
|
|
336
345
|
logger.success?.(`Dev server running at ${url}`);
|
|
337
346
|
logger.info?.(`\nServing from:`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invalidate.d.ts","sourceRoot":"","sources":["../../src/core/invalidate.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/core/invalidate.js
CHANGED
|
@@ -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 =
|
|
284
|
+
const cacheDir = resolveCacheDir();
|
|
285
285
|
// Load existing cache manifest
|
|
286
|
-
|
|
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":"
|
|
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
|
|
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,
|
|
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"}
|
package/dist/core/isg/deps.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../../src/core/isg/deps.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/core/isg/deps.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { join, dirname, relative, posix } from 'path';
|
|
2
|
-
import
|
|
3
|
-
const { pathExists, readFile } = fse;
|
|
2
|
+
import { pathExists, readFile } from '../utils/fs.js';
|
|
4
3
|
import glob from 'fast-glob';
|
|
4
|
+
import { TEMPLATE_EXTENSION } from '../../constants.js';
|
|
5
|
+
import { isCollectionIndexPage, discoverLayout } from '../utils/template-discovery.js';
|
|
6
|
+
import { resolveSrcDir } from '../utils/paths.js';
|
|
5
7
|
/**
|
|
6
8
|
* Error thrown when a circular dependency is detected in templates.
|
|
7
9
|
*/
|
|
@@ -42,7 +44,7 @@ export async function trackTemplateDependencies(page, config) {
|
|
|
42
44
|
return [];
|
|
43
45
|
}
|
|
44
46
|
const deps = [];
|
|
45
|
-
const srcDir =
|
|
47
|
+
const srcDir = resolveSrcDir(config);
|
|
46
48
|
const relativePath = relative(srcDir, page.sourcePath);
|
|
47
49
|
// Track dependencies with circular detection
|
|
48
50
|
const visited = new Set();
|
|
@@ -80,7 +82,7 @@ export async function findPartialDependencies(pagePath, config) {
|
|
|
80
82
|
return [];
|
|
81
83
|
}
|
|
82
84
|
const deps = [];
|
|
83
|
-
const srcDir =
|
|
85
|
+
const srcDir = resolveSrcDir(config);
|
|
84
86
|
// Get the directory of the current page
|
|
85
87
|
const pageDir = dirname(pagePath);
|
|
86
88
|
const pathSegments = pageDir === '.' ? [] : pageDir.split(/[/\\]/);
|
|
@@ -135,71 +137,13 @@ export async function findPartialDependencies(pagePath, config) {
|
|
|
135
137
|
* ```
|
|
136
138
|
*/
|
|
137
139
|
export async function resolveTemplatePath(layout, config) {
|
|
138
|
-
const srcDir =
|
|
140
|
+
const srcDir = resolveSrcDir(config);
|
|
139
141
|
const layoutPath = join(srcDir, `${layout}.eta`);
|
|
140
142
|
if (await pathExists(layoutPath)) {
|
|
141
143
|
return layoutPath;
|
|
142
144
|
}
|
|
143
145
|
return null;
|
|
144
146
|
}
|
|
145
|
-
/**
|
|
146
|
-
* Helper function to determine if a page is a collection index page.
|
|
147
|
-
* Duplicated from templates.ts to avoid circular dependencies.
|
|
148
|
-
*/
|
|
149
|
-
function isCollectionIndexPage(page) {
|
|
150
|
-
// This is a simplified version - in a real implementation,
|
|
151
|
-
// we'd need access to all pages to determine this properly.
|
|
152
|
-
// For now, we'll assume any page ending in /index or at root is a collection page.
|
|
153
|
-
return page.url === '/' || page.url.endsWith('/index') || page.slug === 'index';
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Helper function to discover layout files.
|
|
157
|
-
* Duplicated from templates.ts to avoid circular dependencies.
|
|
158
|
-
*/
|
|
159
|
-
async function discoverLayout(pagePath, config, explicitLayout, isIndexPage) {
|
|
160
|
-
// Early return if required config values are missing
|
|
161
|
-
if (!config.srcDir) {
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
const srcDir = join(process.cwd(), config.srcDir);
|
|
165
|
-
// If explicit layout is specified, use it
|
|
166
|
-
if (explicitLayout) {
|
|
167
|
-
const layoutPath = join(srcDir, `${explicitLayout}.eta`);
|
|
168
|
-
if (await pathExists(layoutPath)) {
|
|
169
|
-
return `${explicitLayout}.eta`;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// Get the directory of the current page
|
|
173
|
-
const pageDir = dirname(pagePath);
|
|
174
|
-
const pathSegments = pageDir === '.' ? [] : pageDir.split(/[/\\]/);
|
|
175
|
-
// Search for layout.eta from current directory up to root
|
|
176
|
-
const dirsToSearch = [];
|
|
177
|
-
// Add current directory if not root
|
|
178
|
-
if (pathSegments.length > 0) {
|
|
179
|
-
for (let i = pathSegments.length; i > 0; i--) {
|
|
180
|
-
dirsToSearch.push(pathSegments.slice(0, i).join('/'));
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// Add root directory
|
|
184
|
-
dirsToSearch.push('');
|
|
185
|
-
for (const dir of dirsToSearch) {
|
|
186
|
-
// For index pages, first check for index.eta in each directory
|
|
187
|
-
if (isIndexPage) {
|
|
188
|
-
const indexLayoutPath = dir ? join(srcDir, dir, 'index.eta') : join(srcDir, 'index.eta');
|
|
189
|
-
if (await pathExists(indexLayoutPath)) {
|
|
190
|
-
const relativePath = dir ? `${dir}/index.eta` : 'index.eta';
|
|
191
|
-
return posix.normalize(relativePath);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// Then check for layout.eta as fallback
|
|
195
|
-
const layoutPath = dir ? join(srcDir, dir, 'layout.eta') : join(srcDir, 'layout.eta');
|
|
196
|
-
if (await pathExists(layoutPath)) {
|
|
197
|
-
const relativePath = dir ? `${dir}/layout.eta` : 'layout.eta';
|
|
198
|
-
return posix.normalize(relativePath);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
147
|
/**
|
|
204
148
|
* Detects circular dependencies in template includes/extends.
|
|
205
149
|
* Uses DFS to traverse the dependency graph and detect cycles.
|
|
@@ -233,6 +177,9 @@ async function detectCircularDependencies(templatePath, srcDir, visited, current
|
|
|
233
177
|
try {
|
|
234
178
|
// Read template content to find includes/extends
|
|
235
179
|
const content = await readFile(templatePath, 'utf-8');
|
|
180
|
+
if (!content) {
|
|
181
|
+
return; // Skip if file doesn't exist
|
|
182
|
+
}
|
|
236
183
|
const dependencies = await parseTemplateDependencies(content, templatePath, srcDir);
|
|
237
184
|
// Recursively check dependencies
|
|
238
185
|
for (const depPath of dependencies) {
|
|
@@ -264,6 +211,7 @@ async function detectCircularDependencies(templatePath, srcDir, visited, current
|
|
|
264
211
|
*/
|
|
265
212
|
async function parseTemplateDependencies(content, templatePath, srcDir) {
|
|
266
213
|
const dependencies = [];
|
|
214
|
+
const templateDir = dirname(templatePath);
|
|
267
215
|
// Look for Eta include patterns: <%~ include('template') %>
|
|
268
216
|
const includePatterns = [
|
|
269
217
|
/<%[~-]?\s*include\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
|
|
@@ -274,7 +222,7 @@ async function parseTemplateDependencies(content, templatePath, srcDir) {
|
|
|
274
222
|
while ((match = pattern.exec(content)) !== null) {
|
|
275
223
|
const includePath = match[1];
|
|
276
224
|
if (includePath) {
|
|
277
|
-
const resolvedPath = await resolveTemplatePathInternal(includePath, srcDir);
|
|
225
|
+
const resolvedPath = await resolveTemplatePathInternal(includePath, srcDir, templateDir);
|
|
278
226
|
if (resolvedPath) {
|
|
279
227
|
dependencies.push(resolvedPath);
|
|
280
228
|
}
|
|
@@ -291,7 +239,7 @@ async function parseTemplateDependencies(content, templatePath, srcDir) {
|
|
|
291
239
|
while ((match = pattern.exec(content)) !== null) {
|
|
292
240
|
const layoutPath = match[1];
|
|
293
241
|
if (layoutPath) {
|
|
294
|
-
const resolvedPath = await resolveTemplatePathInternal(layoutPath, srcDir);
|
|
242
|
+
const resolvedPath = await resolveTemplatePathInternal(layoutPath, srcDir, templateDir);
|
|
295
243
|
if (resolvedPath) {
|
|
296
244
|
dependencies.push(resolvedPath);
|
|
297
245
|
}
|
|
@@ -306,26 +254,59 @@ async function parseTemplateDependencies(content, templatePath, srcDir) {
|
|
|
306
254
|
*
|
|
307
255
|
* @param templateRef - Template reference from include/layout statement
|
|
308
256
|
* @param srcDir - Source directory for resolving paths
|
|
257
|
+
* @param currentDir - Current directory for hierarchical search (optional)
|
|
309
258
|
* @returns Absolute path to template file, or null if not found
|
|
310
259
|
*/
|
|
311
|
-
async function resolveTemplatePathInternal(templateRef, srcDir) {
|
|
312
|
-
|
|
313
|
-
|
|
260
|
+
async function resolveTemplatePathInternal(templateRef, srcDir, currentDir) {
|
|
261
|
+
const templateName = templateRef.endsWith(TEMPLATE_EXTENSION)
|
|
262
|
+
? templateRef
|
|
263
|
+
: `${templateRef}${TEMPLATE_EXTENSION}`;
|
|
314
264
|
// Try absolute path from srcDir
|
|
315
265
|
const absolutePath = join(srcDir, templateName);
|
|
316
266
|
if (await pathExists(absolutePath)) {
|
|
317
267
|
return absolutePath;
|
|
318
268
|
}
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
269
|
+
// Determine the starting directory for hierarchical search (relative to srcDir)
|
|
270
|
+
const startDir = currentDir ? relative(srcDir, currentDir) : '';
|
|
271
|
+
// Build list of directories to search (current dir up to srcDir root only)
|
|
272
|
+
// This ensures we never search outside the source directory boundary
|
|
273
|
+
const dirsToSearch = [];
|
|
274
|
+
// Safety check: ensure currentDir is within srcDir (relative path shouldn't start with '..')
|
|
275
|
+
if (startDir.startsWith('..')) {
|
|
276
|
+
// If currentDir is outside srcDir, only search at srcDir root
|
|
277
|
+
dirsToSearch.push('');
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
const pathSegments = startDir === '' ? [] : startDir.split(/[/\\]/);
|
|
281
|
+
// Add directories from current up to srcDir root (don't go beyond srcDir)
|
|
282
|
+
if (pathSegments.length > 0) {
|
|
283
|
+
for (let i = pathSegments.length; i >= 0; i--) {
|
|
284
|
+
if (i === 0) {
|
|
285
|
+
dirsToSearch.push(''); // srcDir root directory
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
dirsToSearch.push(pathSegments.slice(0, i).join('/'));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
dirsToSearch.push(''); // srcDir root directory only
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// Search each directory level for the template in underscore directories
|
|
297
|
+
for (const dir of dirsToSearch) {
|
|
298
|
+
const searchDir = dir ? join(srcDir, dir) : srcDir;
|
|
299
|
+
try {
|
|
300
|
+
// Search for template in underscore directories at this level only
|
|
301
|
+
const pattern = posix.join(searchDir.replace(/\\/g, '/'), '_*', templateName);
|
|
302
|
+
const matches = await glob(pattern, { absolute: true });
|
|
303
|
+
if (matches.length > 0) {
|
|
304
|
+
return matches[0]; // Return first match
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// Continue if directory doesn't exist or can't be read
|
|
309
|
+
continue;
|
|
329
310
|
}
|
|
330
311
|
}
|
|
331
312
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../../src/core/isg/hash.ts"],"names":[],"mappings":"
|
|
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,CAKhG;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"}
|