@stati/core 1.3.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/core/dev.d.ts.map +1 -1
- package/dist/core/dev.js +176 -48
- package/dist/core/isg/hash.d.ts.map +1 -1
- package/dist/core/isg/hash.js +13 -1
- package/dist/core/markdown.d.ts.map +1 -1
- package/dist/core/markdown.js +21 -2
- package/dist/core/preview.d.ts +19 -0
- package/dist/core/preview.d.ts.map +1 -0
- package/dist/core/preview.js +159 -0
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +83 -11
- package/dist/core/utils/error-overlay.d.ts +31 -0
- package/dist/core/utils/error-overlay.d.ts.map +1 -0
- package/dist/core/utils/error-overlay.js +562 -0
- package/dist/core/utils/partial-validation.d.ts +6 -0
- package/dist/core/utils/partial-validation.d.ts.map +1 -0
- package/dist/core/utils/partial-validation.js +129 -0
- package/dist/core/utils/server.d.ts +23 -0
- package/dist/core/utils/server.d.ts.map +1 -0
- package/dist/core/utils/server.js +61 -0
- package/dist/core/utils/template-errors.d.ts +28 -0
- package/dist/core/utils/template-errors.d.ts.map +1 -0
- package/dist/core/utils/template-errors.js +128 -0
- package/dist/core/utils/version.d.ts +6 -0
- package/dist/core/utils/version.d.ts.map +1 -0
- package/dist/core/utils/version.js +20 -0
- package/dist/env.d.ts +3 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +7 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/core/dev.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/core/dev.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/core/dev.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAa7D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb;AAoOD,wBAAsB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC,CA2XxF"}
|
package/dist/core/dev.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { createServer } from 'http';
|
|
2
2
|
import { join, extname } from 'path';
|
|
3
3
|
import { posix } from 'path';
|
|
4
|
-
import { readFile
|
|
4
|
+
import { readFile } 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';
|
|
10
11
|
import { resolveDevPaths, resolveCacheDir } from './utils/paths.js';
|
|
12
|
+
import { resolvePrettyUrl } from './utils/server.js';
|
|
13
|
+
import { createErrorOverlay, parseErrorDetails } from './utils/error-overlay.js';
|
|
14
|
+
import { TemplateError } from './utils/template-errors.js';
|
|
15
|
+
import { setEnv, getEnv } from '../env.js';
|
|
11
16
|
import { DEFAULT_DEV_PORT, DEFAULT_DEV_HOST, TEMPLATE_EXTENSION } from '../constants.js';
|
|
12
17
|
/**
|
|
13
18
|
* Loads and validates configuration for the dev server.
|
|
@@ -32,24 +37,35 @@ async function loadDevConfig(configPath, logger) {
|
|
|
32
37
|
/**
|
|
33
38
|
* Performs an initial build to ensure dist/ exists
|
|
34
39
|
*/
|
|
35
|
-
async function performInitialBuild(configPath, logger) {
|
|
40
|
+
async function performInitialBuild(configPath, logger, onError) {
|
|
36
41
|
try {
|
|
42
|
+
// Clear cache to ensure fresh build on dev server start
|
|
43
|
+
logger.info?.('Clearing cache for fresh development build...');
|
|
44
|
+
await invalidate();
|
|
37
45
|
await build({
|
|
38
46
|
logger,
|
|
39
47
|
force: false,
|
|
40
48
|
clean: false,
|
|
41
49
|
...(configPath && { configPath }),
|
|
42
50
|
});
|
|
51
|
+
// Clear any previous errors on successful build
|
|
52
|
+
if (onError) {
|
|
53
|
+
onError(null);
|
|
54
|
+
}
|
|
43
55
|
}
|
|
44
56
|
catch (error) {
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
const buildError = error instanceof Error ? error : new Error(String(error));
|
|
58
|
+
logger.error?.(`Initial build failed: ${buildError.message}`);
|
|
59
|
+
// Store the error for display in browser - DON'T clear it even if partial rebuild succeeds
|
|
60
|
+
onError?.(buildError);
|
|
61
|
+
// In development, don't throw - let the dev server start and show errors in browser
|
|
62
|
+
logger.warning?.('Dev server will start with build errors. Check browser for error overlay.');
|
|
47
63
|
}
|
|
48
64
|
}
|
|
49
65
|
/**
|
|
50
66
|
* Performs incremental rebuild when files change, using ISG logic for smart rebuilds
|
|
51
67
|
*/
|
|
52
|
-
async function performIncrementalRebuild(changedPath, configPath, logger, wsServer, isBuildingRef) {
|
|
68
|
+
async function performIncrementalRebuild(changedPath, configPath, logger, wsServer, isBuildingRef, onError) {
|
|
53
69
|
if (isBuildingRef.value) {
|
|
54
70
|
logger.info?.('Build in progress, skipping...');
|
|
55
71
|
return;
|
|
@@ -84,6 +100,10 @@ async function performIncrementalRebuild(changedPath, configPath, logger, wsServ
|
|
|
84
100
|
...(configPath && { configPath }),
|
|
85
101
|
});
|
|
86
102
|
}
|
|
103
|
+
// Clear any previous errors on successful build
|
|
104
|
+
if (onError) {
|
|
105
|
+
onError(null);
|
|
106
|
+
}
|
|
87
107
|
// Notify all connected clients to reload
|
|
88
108
|
if (wsServer) {
|
|
89
109
|
wsServer.clients.forEach((client) => {
|
|
@@ -98,8 +118,13 @@ async function performIncrementalRebuild(changedPath, configPath, logger, wsServ
|
|
|
98
118
|
logger.info?.(`⚡ ${relativePath} rebuilt in ${duration}ms`);
|
|
99
119
|
}
|
|
100
120
|
catch (error) {
|
|
121
|
+
const buildError = error instanceof Error ? error : new Error(String(error));
|
|
101
122
|
const duration = Date.now() - startTime;
|
|
102
|
-
logger.error?.(`❌ Rebuild failed after ${duration}ms: ${
|
|
123
|
+
logger.error?.(`❌ Rebuild failed after ${duration}ms: ${buildError.message}`);
|
|
124
|
+
// Store the error for display in browser
|
|
125
|
+
if (onError) {
|
|
126
|
+
onError(buildError);
|
|
127
|
+
}
|
|
103
128
|
}
|
|
104
129
|
finally {
|
|
105
130
|
isBuildingRef.value = false;
|
|
@@ -125,8 +150,14 @@ async function handleTemplateChange(templatePath, configPath, logger) {
|
|
|
125
150
|
}
|
|
126
151
|
// Find pages that depend on this template
|
|
127
152
|
const affectedPages = [];
|
|
153
|
+
const normalizedTemplatePath = posix.normalize(templatePath.replace(/\\/g, '/'));
|
|
128
154
|
for (const [pagePath, entry] of Object.entries(cacheManifest.entries)) {
|
|
129
|
-
if (entry.deps.some((dep) =>
|
|
155
|
+
if (entry.deps.some((dep) => {
|
|
156
|
+
const normalizedDep = posix.normalize(dep.replace(/\\/g, '/'));
|
|
157
|
+
// Use endsWith for more precise matching to avoid false positives
|
|
158
|
+
return (normalizedDep === normalizedTemplatePath ||
|
|
159
|
+
normalizedDep.endsWith('/' + normalizedTemplatePath));
|
|
160
|
+
})) {
|
|
130
161
|
affectedPages.push(pagePath);
|
|
131
162
|
// Remove from cache to force rebuild
|
|
132
163
|
delete cacheManifest.entries[pagePath];
|
|
@@ -143,15 +174,31 @@ async function handleTemplateChange(templatePath, configPath, logger) {
|
|
|
143
174
|
...(configPath && { configPath }),
|
|
144
175
|
});
|
|
145
176
|
}
|
|
177
|
+
else {
|
|
178
|
+
// If no affected pages were found but a template changed,
|
|
179
|
+
// force a full rebuild to ensure changes are reflected
|
|
180
|
+
// This can happen if dependency tracking missed something
|
|
181
|
+
await build({
|
|
182
|
+
logger,
|
|
183
|
+
force: true,
|
|
184
|
+
clean: false,
|
|
185
|
+
...(configPath && { configPath }),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
146
188
|
}
|
|
147
|
-
catch {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
189
|
+
catch (_error) {
|
|
190
|
+
try {
|
|
191
|
+
// Fallback to full rebuild
|
|
192
|
+
await build({
|
|
193
|
+
logger,
|
|
194
|
+
force: false,
|
|
195
|
+
clean: false,
|
|
196
|
+
...(configPath && { configPath }),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch (fallbackError) {
|
|
200
|
+
throw fallbackError instanceof Error ? fallbackError : new Error(String(fallbackError));
|
|
201
|
+
}
|
|
155
202
|
}
|
|
156
203
|
}
|
|
157
204
|
export async function createDevServer(options = {}) {
|
|
@@ -164,9 +211,16 @@ export async function createDevServer(options = {}) {
|
|
|
164
211
|
processing: (msg) => console.log(msg),
|
|
165
212
|
stats: (msg) => console.log(msg),
|
|
166
213
|
}, } = options;
|
|
214
|
+
setEnv('development');
|
|
167
215
|
const url = `http://${host}:${port}`;
|
|
168
216
|
let httpServer = null;
|
|
169
217
|
let wsServer = null;
|
|
218
|
+
// Track build errors for display in browser
|
|
219
|
+
let lastBuildError = null;
|
|
220
|
+
// Function to set build errors for error overlay display
|
|
221
|
+
const setLastBuildError = (error) => {
|
|
222
|
+
lastBuildError = error;
|
|
223
|
+
};
|
|
170
224
|
let watcher = null;
|
|
171
225
|
const isBuildingRef = { value: false };
|
|
172
226
|
// Load configuration
|
|
@@ -232,24 +286,90 @@ export async function createDevServer(options = {}) {
|
|
|
232
286
|
* Serves files from the dist directory
|
|
233
287
|
*/
|
|
234
288
|
async function serveFile(requestPath) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
289
|
+
// Use the global build error
|
|
290
|
+
const errorToShow = lastBuildError;
|
|
291
|
+
// If there's a build error, show error overlay for HTML requests
|
|
292
|
+
if (errorToShow &&
|
|
293
|
+
(requestPath === '/' || requestPath.endsWith('.html') || !requestPath.includes('.'))) {
|
|
294
|
+
let errorDetails;
|
|
295
|
+
// Use enhanced error details for TemplateError instances
|
|
296
|
+
if (errorToShow instanceof TemplateError) {
|
|
297
|
+
errorDetails = await errorToShow.toErrorDetails();
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
// Determine error type based on error message
|
|
301
|
+
const message = errorToShow.message.toLowerCase();
|
|
302
|
+
const name = errorToShow.name ? errorToShow.name.toLowerCase() : '';
|
|
303
|
+
const errorType = message.includes('template') ||
|
|
304
|
+
message.includes('eta') ||
|
|
305
|
+
message.includes('layout') ||
|
|
306
|
+
name.includes('template')
|
|
307
|
+
? 'template'
|
|
308
|
+
: message.includes('front-matter') ||
|
|
309
|
+
message.includes('yaml') ||
|
|
310
|
+
name.includes('yaml') ||
|
|
311
|
+
name.includes('yamlexception')
|
|
312
|
+
? 'markdown'
|
|
313
|
+
: message.includes('config') || name.includes('config')
|
|
314
|
+
? 'config'
|
|
315
|
+
: 'build';
|
|
316
|
+
errorDetails = parseErrorDetails(errorToShow, errorType);
|
|
317
|
+
}
|
|
318
|
+
const errorHtml = createErrorOverlay(errorDetails, requestPath);
|
|
319
|
+
return {
|
|
320
|
+
content: errorHtml,
|
|
321
|
+
mimeType: 'text/html',
|
|
322
|
+
statusCode: 500,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
const originalFilePath = join(outDir, requestPath === '/' ? 'index.html' : requestPath);
|
|
326
|
+
// Use the shared pretty URL resolver
|
|
327
|
+
const { filePath, found } = await resolvePrettyUrl(outDir, requestPath, originalFilePath);
|
|
328
|
+
if (!found || !filePath) {
|
|
329
|
+
// If we have a build error and this is an HTML request, show error overlay instead of 404
|
|
330
|
+
const errorToShow = lastBuildError;
|
|
331
|
+
if (errorToShow &&
|
|
332
|
+
(requestPath === '/' || requestPath.endsWith('.html') || !requestPath.includes('.'))) {
|
|
333
|
+
// Use enhanced error details for TemplateError instances
|
|
334
|
+
let errorDetails;
|
|
335
|
+
if (errorToShow instanceof TemplateError) {
|
|
336
|
+
errorDetails = await errorToShow.toErrorDetails();
|
|
244
337
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
338
|
+
else {
|
|
339
|
+
// Determine error type based on error message
|
|
340
|
+
const message = errorToShow.message.toLowerCase();
|
|
341
|
+
const name = errorToShow.name ? errorToShow.name.toLowerCase() : '';
|
|
342
|
+
const errorType = message.includes('template') ||
|
|
343
|
+
message.includes('eta') ||
|
|
344
|
+
message.includes('layout') ||
|
|
345
|
+
name.includes('template')
|
|
346
|
+
? 'template'
|
|
347
|
+
: message.includes('front-matter') ||
|
|
348
|
+
message.includes('yaml') ||
|
|
349
|
+
name.includes('yaml') ||
|
|
350
|
+
name.includes('yamlexception')
|
|
351
|
+
? 'markdown'
|
|
352
|
+
: message.includes('config') || name.includes('config')
|
|
353
|
+
? 'config'
|
|
354
|
+
: 'build';
|
|
355
|
+
errorDetails = parseErrorDetails(errorToShow, errorType);
|
|
251
356
|
}
|
|
357
|
+
const errorHtml = createErrorOverlay(errorDetails, requestPath);
|
|
358
|
+
return {
|
|
359
|
+
content: errorHtml,
|
|
360
|
+
mimeType: 'text/html',
|
|
361
|
+
statusCode: 500,
|
|
362
|
+
};
|
|
252
363
|
}
|
|
364
|
+
return {
|
|
365
|
+
content: requestPath.endsWith('/')
|
|
366
|
+
? '404 - Directory listing not available'
|
|
367
|
+
: '404 - File not found',
|
|
368
|
+
mimeType: 'text/plain',
|
|
369
|
+
statusCode: 404,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
try {
|
|
253
373
|
const mimeType = getMimeType(filePath);
|
|
254
374
|
const content = await readFile(filePath);
|
|
255
375
|
// Inject live reload script into HTML files
|
|
@@ -269,11 +389,11 @@ export async function createDevServer(options = {}) {
|
|
|
269
389
|
};
|
|
270
390
|
}
|
|
271
391
|
catch {
|
|
272
|
-
//
|
|
392
|
+
// This should rarely happen since resolvePrettyUrl already checked the file exists
|
|
273
393
|
return {
|
|
274
|
-
content: '
|
|
394
|
+
content: '500 - Error reading file',
|
|
275
395
|
mimeType: 'text/plain',
|
|
276
|
-
statusCode:
|
|
396
|
+
statusCode: 500,
|
|
277
397
|
};
|
|
278
398
|
}
|
|
279
399
|
}
|
|
@@ -281,7 +401,7 @@ export async function createDevServer(options = {}) {
|
|
|
281
401
|
url,
|
|
282
402
|
async start() {
|
|
283
403
|
// Perform initial build
|
|
284
|
-
await performInitialBuild(configPath, logger);
|
|
404
|
+
await performInitialBuild(configPath, logger, setLastBuildError);
|
|
285
405
|
// Create HTTP server
|
|
286
406
|
httpServer = createServer(async (req, res) => {
|
|
287
407
|
const requestPath = req.url || '/';
|
|
@@ -306,17 +426,25 @@ export async function createDevServer(options = {}) {
|
|
|
306
426
|
}
|
|
307
427
|
});
|
|
308
428
|
// Create WebSocket server for live reload
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
429
|
+
if (getEnv() !== 'test') {
|
|
430
|
+
try {
|
|
431
|
+
wsServer = new WebSocketServer({
|
|
432
|
+
server: httpServer,
|
|
433
|
+
path: '/__ws',
|
|
434
|
+
});
|
|
435
|
+
wsServer.on('connection', (ws) => {
|
|
436
|
+
logger.info?.('Browser connected for live reload');
|
|
437
|
+
const websocket = ws;
|
|
438
|
+
websocket.on('close', () => {
|
|
439
|
+
logger.info?.('Browser disconnected from live reload');
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
catch (_error) {
|
|
444
|
+
logger.warning?.('WebSocket server creation failed, live reload will not be available');
|
|
445
|
+
wsServer = null;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
320
448
|
// Start HTTP server
|
|
321
449
|
await new Promise((resolve, reject) => {
|
|
322
450
|
httpServer.listen(port, host, () => {
|
|
@@ -334,13 +462,13 @@ export async function createDevServer(options = {}) {
|
|
|
334
462
|
ignoreInitial: true,
|
|
335
463
|
});
|
|
336
464
|
watcher.on('change', (path) => {
|
|
337
|
-
void performIncrementalRebuild(path, configPath, logger, wsServer, isBuildingRef);
|
|
465
|
+
void performIncrementalRebuild(path, configPath, logger, wsServer, isBuildingRef, setLastBuildError);
|
|
338
466
|
});
|
|
339
467
|
watcher.on('add', (path) => {
|
|
340
|
-
void performIncrementalRebuild(path, configPath, logger, wsServer, isBuildingRef);
|
|
468
|
+
void performIncrementalRebuild(path, configPath, logger, wsServer, isBuildingRef, setLastBuildError);
|
|
341
469
|
});
|
|
342
470
|
watcher.on('unlink', (path) => {
|
|
343
|
-
void performIncrementalRebuild(path, configPath, logger, wsServer, isBuildingRef);
|
|
471
|
+
void performIncrementalRebuild(path, configPath, logger, wsServer, isBuildingRef, setLastBuildError);
|
|
344
472
|
});
|
|
345
473
|
logger.success?.(`Dev server running at ${url}`);
|
|
346
474
|
logger.info?.(`\nServing from:`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../../src/core/isg/hash.ts"],"names":[],"mappings":"AAwBA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../../src/core/isg/hash.ts"],"names":[],"mappings":"AAwBA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAiBhG;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiB9E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAKnF"}
|
package/dist/core/isg/hash.js
CHANGED
|
@@ -35,7 +35,19 @@ function createSha256Hash(data) {
|
|
|
35
35
|
*/
|
|
36
36
|
export function computeContentHash(content, frontMatter) {
|
|
37
37
|
// Hash the front matter (sorted for consistency)
|
|
38
|
-
|
|
38
|
+
// Note: We need to sort keys at all levels, not just top level
|
|
39
|
+
const sortKeys = (obj) => {
|
|
40
|
+
if (obj === null || typeof obj !== 'object')
|
|
41
|
+
return obj;
|
|
42
|
+
if (Array.isArray(obj))
|
|
43
|
+
return obj.map(sortKeys);
|
|
44
|
+
const sorted = {};
|
|
45
|
+
for (const key of Object.keys(obj).sort()) {
|
|
46
|
+
sorted[key] = sortKeys(obj[key]);
|
|
47
|
+
}
|
|
48
|
+
return sorted;
|
|
49
|
+
};
|
|
50
|
+
const sortedFrontMatter = JSON.stringify(sortKeys(frontMatter));
|
|
39
51
|
return createSha256Hash([content, sortedFrontMatter]);
|
|
40
52
|
}
|
|
41
53
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/core/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/core/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,aAAa,CAAC;AAIrC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAmBrD;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAuCtF;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,MAAM,CAEtE"}
|
package/dist/core/markdown.js
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
import MarkdownIt from 'markdown-it';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import { pathToFileURL } from 'url';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
/**
|
|
6
|
+
* Load a markdown plugin, trying different resolution strategies
|
|
7
|
+
*/
|
|
8
|
+
async function loadMarkdownPlugin(pluginName) {
|
|
9
|
+
const fullPluginName = `markdown-it-${pluginName}`;
|
|
10
|
+
// Try importing from current working directory first (for projects using Stati)
|
|
11
|
+
try {
|
|
12
|
+
const require = createRequire(pathToFileURL(path.resolve(process.cwd(), 'package.json')));
|
|
13
|
+
const pluginPath = require.resolve(fullPluginName);
|
|
14
|
+
return await import(pathToFileURL(pluginPath).href);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Fallback to standard resolution (for core package dependencies)
|
|
18
|
+
return await import(fullPluginName);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
2
21
|
/**
|
|
3
22
|
* Creates and configures a MarkdownIt processor based on the provided configuration.
|
|
4
23
|
* Supports both plugin array format and configure function format.
|
|
@@ -15,7 +34,7 @@ export async function createMarkdownProcessor(config) {
|
|
|
15
34
|
if (typeof plugin === 'string') {
|
|
16
35
|
// Plugin name only
|
|
17
36
|
try {
|
|
18
|
-
const pluginModule = await
|
|
37
|
+
const pluginModule = await loadMarkdownPlugin(plugin);
|
|
19
38
|
const pluginFunction = pluginModule.default || pluginModule;
|
|
20
39
|
md.use(pluginFunction);
|
|
21
40
|
}
|
|
@@ -27,7 +46,7 @@ export async function createMarkdownProcessor(config) {
|
|
|
27
46
|
// Plugin name with options [name, options]
|
|
28
47
|
const [pluginName, options] = plugin;
|
|
29
48
|
try {
|
|
30
|
-
const pluginModule = await
|
|
49
|
+
const pluginModule = await loadMarkdownPlugin(pluginName);
|
|
31
50
|
const pluginFunction = pluginModule.default || pluginModule;
|
|
32
51
|
md.use(pluginFunction, options);
|
|
33
52
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Logger } from '../types/index.js';
|
|
2
|
+
export interface PreviewServerOptions {
|
|
3
|
+
port?: number;
|
|
4
|
+
host?: string;
|
|
5
|
+
open?: boolean;
|
|
6
|
+
configPath?: string;
|
|
7
|
+
logger?: Logger;
|
|
8
|
+
}
|
|
9
|
+
export interface PreviewServer {
|
|
10
|
+
start(): Promise<void>;
|
|
11
|
+
stop(): Promise<void>;
|
|
12
|
+
url: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates a preview server that serves the built site from the dist directory
|
|
16
|
+
* without live reload functionality, perfect for previewing the production build.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createPreviewServer(options?: PreviewServerOptions): Promise<PreviewServer>;
|
|
19
|
+
//# sourceMappingURL=preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/core/preview.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAMhD,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb;AAyBD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,aAAa,CAAC,CA2JxB"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import { join, extname } from 'path';
|
|
3
|
+
import { readFile } from 'fs/promises';
|
|
4
|
+
import { loadConfig } from '../config/loader.js';
|
|
5
|
+
import { resolveDevPaths } from './utils/paths.js';
|
|
6
|
+
import { resolvePrettyUrl } from './utils/server.js';
|
|
7
|
+
import { DEFAULT_DEV_PORT, DEFAULT_DEV_HOST } from '../constants.js';
|
|
8
|
+
/**
|
|
9
|
+
* Loads and validates configuration for the preview server.
|
|
10
|
+
*/
|
|
11
|
+
async function loadPreviewConfig(configPath, logger) {
|
|
12
|
+
try {
|
|
13
|
+
if (configPath) {
|
|
14
|
+
logger.info?.(`Loading config from: ${configPath}`);
|
|
15
|
+
}
|
|
16
|
+
const config = await loadConfig(process.cwd());
|
|
17
|
+
const { outDir } = resolveDevPaths(config);
|
|
18
|
+
return { outDir };
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
logger.error?.(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Creates a preview server that serves the built site from the dist directory
|
|
27
|
+
* without live reload functionality, perfect for previewing the production build.
|
|
28
|
+
*/
|
|
29
|
+
export async function createPreviewServer(options = {}) {
|
|
30
|
+
const { port = DEFAULT_DEV_PORT, host = DEFAULT_DEV_HOST, open = false, configPath, logger = {
|
|
31
|
+
info: () => { },
|
|
32
|
+
success: () => { },
|
|
33
|
+
error: (msg) => console.error(msg),
|
|
34
|
+
warning: (msg) => console.warn(msg),
|
|
35
|
+
building: () => { },
|
|
36
|
+
processing: () => { },
|
|
37
|
+
stats: () => { },
|
|
38
|
+
}, } = options;
|
|
39
|
+
const url = `http://${host}:${port}`;
|
|
40
|
+
let httpServer = null;
|
|
41
|
+
// Load configuration
|
|
42
|
+
const { outDir } = await loadPreviewConfig(configPath, logger);
|
|
43
|
+
/**
|
|
44
|
+
* Gets MIME type for a file based on its extension
|
|
45
|
+
*/
|
|
46
|
+
function getMimeType(filePath) {
|
|
47
|
+
const ext = extname(filePath).toLowerCase();
|
|
48
|
+
const mimeTypes = {
|
|
49
|
+
'.html': 'text/html',
|
|
50
|
+
'.js': 'application/javascript',
|
|
51
|
+
'.css': 'text/css',
|
|
52
|
+
'.json': 'application/json',
|
|
53
|
+
'.png': 'image/png',
|
|
54
|
+
'.jpg': 'image/jpeg',
|
|
55
|
+
'.jpeg': 'image/jpeg',
|
|
56
|
+
'.gif': 'image/gif',
|
|
57
|
+
'.svg': 'image/svg+xml',
|
|
58
|
+
'.ico': 'image/x-icon',
|
|
59
|
+
'.webp': 'image/webp',
|
|
60
|
+
'.woff': 'font/woff',
|
|
61
|
+
'.woff2': 'font/woff2',
|
|
62
|
+
'.ttf': 'font/ttf',
|
|
63
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
64
|
+
};
|
|
65
|
+
return mimeTypes[ext] || 'application/octet-stream';
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Serves files from the dist directory
|
|
69
|
+
*/
|
|
70
|
+
async function serveFile(requestPath) {
|
|
71
|
+
const originalFilePath = join(outDir, requestPath === '/' ? 'index.html' : requestPath);
|
|
72
|
+
// Use the shared pretty URL resolver
|
|
73
|
+
const { filePath, found } = await resolvePrettyUrl(outDir, requestPath, originalFilePath);
|
|
74
|
+
if (!found || !filePath) {
|
|
75
|
+
return {
|
|
76
|
+
content: requestPath.endsWith('/')
|
|
77
|
+
? '404 - Directory listing not available'
|
|
78
|
+
: '404 - File not found',
|
|
79
|
+
mimeType: 'text/plain',
|
|
80
|
+
statusCode: 404,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const mimeType = getMimeType(filePath);
|
|
85
|
+
const content = await readFile(filePath);
|
|
86
|
+
// Unlike dev server, we don't inject live reload script in preview mode
|
|
87
|
+
return {
|
|
88
|
+
content,
|
|
89
|
+
mimeType,
|
|
90
|
+
statusCode: 200,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// This should rarely happen since resolvePrettyUrl already checked the file exists
|
|
95
|
+
return {
|
|
96
|
+
content: '500 - Error reading file',
|
|
97
|
+
mimeType: 'text/plain',
|
|
98
|
+
statusCode: 500,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const previewServer = {
|
|
103
|
+
url,
|
|
104
|
+
async start() {
|
|
105
|
+
// Create HTTP server
|
|
106
|
+
httpServer = createServer(async (req, res) => {
|
|
107
|
+
const requestPath = req.url || '/';
|
|
108
|
+
logger.processing?.(`${req.method} ${requestPath}`);
|
|
109
|
+
try {
|
|
110
|
+
const { content, mimeType, statusCode } = await serveFile(requestPath);
|
|
111
|
+
res.writeHead(statusCode, {
|
|
112
|
+
'Content-Type': mimeType,
|
|
113
|
+
'Access-Control-Allow-Origin': '*',
|
|
114
|
+
'Cache-Control': 'public, max-age=31536000', // Better caching for production preview
|
|
115
|
+
});
|
|
116
|
+
res.end(content);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
logger.error?.(`Server error: ${error instanceof Error ? error.message : String(error)}`);
|
|
120
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
121
|
+
res.end('500 - Internal Server Error');
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
// Start HTTP server
|
|
125
|
+
await new Promise((resolve, reject) => {
|
|
126
|
+
httpServer.listen(port, host, () => {
|
|
127
|
+
resolve();
|
|
128
|
+
});
|
|
129
|
+
httpServer.on('error', (error) => {
|
|
130
|
+
reject(error);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
logger.success?.(`Preview server running at ${url}`);
|
|
134
|
+
logger.info?.(`\nServing from:`);
|
|
135
|
+
logger.info?.(` 📁 ${outDir}`);
|
|
136
|
+
// Open browser if requested
|
|
137
|
+
if (open) {
|
|
138
|
+
try {
|
|
139
|
+
const { default: openBrowser } = await import('open');
|
|
140
|
+
await openBrowser(url);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
logger.info?.('Could not open browser automatically');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
async stop() {
|
|
148
|
+
if (httpServer) {
|
|
149
|
+
await new Promise((resolve) => {
|
|
150
|
+
httpServer.close(() => {
|
|
151
|
+
resolve();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
httpServer = null;
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
return previewServer;
|
|
159
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/core/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG1B,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAkB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/core/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG1B,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAkB,MAAM,mBAAmB,CAAC;AAqLzF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,GAAG,CAW7D;AAED,wBAAsB,UAAU,CAC9B,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EACnB,GAAG,EAAE,GAAG,EACR,UAAU,CAAC,EAAE,OAAO,EAAE,EACtB,QAAQ,CAAC,EAAE,SAAS,EAAE,GACrB,OAAO,CAAC,MAAM,CAAC,CA0JjB"}
|