@stati/core 1.3.2 → 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.
@@ -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;AAQ7D,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;AAmLD,wBAAsB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC,CAqQxF"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/core/dev.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAS7D,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;AAyMD,wBAAsB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC,CAgTxF"}
package/dist/core/dev.js CHANGED
@@ -5,6 +5,7 @@ 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';
10
11
  import { resolveDevPaths, resolveCacheDir } from './utils/paths.js';
@@ -34,6 +35,9 @@ async function loadDevConfig(configPath, logger) {
34
35
  */
35
36
  async function performInitialBuild(configPath, logger) {
36
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();
37
41
  await build({
38
42
  logger,
39
43
  force: false,
@@ -125,8 +129,14 @@ async function handleTemplateChange(templatePath, configPath, logger) {
125
129
  }
126
130
  // Find pages that depend on this template
127
131
  const affectedPages = [];
132
+ const normalizedTemplatePath = posix.normalize(templatePath.replace(/\\/g, '/'));
128
133
  for (const [pagePath, entry] of Object.entries(cacheManifest.entries)) {
129
- if (entry.deps.some((dep) => dep.includes(posix.normalize(templatePath.replace(/\\/g, '/'))))) {
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
+ })) {
130
140
  affectedPages.push(pagePath);
131
141
  // Remove from cache to force rebuild
132
142
  delete cacheManifest.entries[pagePath];
@@ -143,6 +153,17 @@ async function handleTemplateChange(templatePath, configPath, logger) {
143
153
  ...(configPath && { configPath }),
144
154
  });
145
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
+ }
146
167
  }
147
168
  catch {
148
169
  // Fallback to full rebuild
@@ -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',
@@ -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;AAKhD,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,CA+JxB"}
@@ -0,0 +1,163 @@
1
+ import { createServer } from 'http';
2
+ import { join, extname } from 'path';
3
+ import { readFile, stat } from 'fs/promises';
4
+ import { loadConfig } from '../config/loader.js';
5
+ import { resolveDevPaths } from './utils/paths.js';
6
+ import { DEFAULT_DEV_PORT, DEFAULT_DEV_HOST } from '../constants.js';
7
+ /**
8
+ * Loads and validates configuration for the preview server.
9
+ */
10
+ async function loadPreviewConfig(configPath, logger) {
11
+ try {
12
+ if (configPath) {
13
+ logger.info?.(`Loading config from: ${configPath}`);
14
+ }
15
+ const config = await loadConfig(process.cwd());
16
+ const { outDir } = resolveDevPaths(config);
17
+ return { outDir };
18
+ }
19
+ catch (error) {
20
+ logger.error?.(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
21
+ throw error;
22
+ }
23
+ }
24
+ /**
25
+ * Creates a preview server that serves the built site from the dist directory
26
+ * without live reload functionality, perfect for previewing the production build.
27
+ */
28
+ export async function createPreviewServer(options = {}) {
29
+ const { port = DEFAULT_DEV_PORT, host = DEFAULT_DEV_HOST, open = false, configPath, logger = {
30
+ info: () => { },
31
+ success: () => { },
32
+ error: (msg) => console.error(msg),
33
+ warning: (msg) => console.warn(msg),
34
+ building: () => { },
35
+ processing: () => { },
36
+ stats: () => { },
37
+ }, } = options;
38
+ const url = `http://${host}:${port}`;
39
+ let httpServer = null;
40
+ // Load configuration
41
+ const { outDir } = await loadPreviewConfig(configPath, logger);
42
+ /**
43
+ * Gets MIME type for a file based on its extension
44
+ */
45
+ function getMimeType(filePath) {
46
+ const ext = extname(filePath).toLowerCase();
47
+ const mimeTypes = {
48
+ '.html': 'text/html',
49
+ '.js': 'application/javascript',
50
+ '.css': 'text/css',
51
+ '.json': 'application/json',
52
+ '.png': 'image/png',
53
+ '.jpg': 'image/jpeg',
54
+ '.jpeg': 'image/jpeg',
55
+ '.gif': 'image/gif',
56
+ '.svg': 'image/svg+xml',
57
+ '.ico': 'image/x-icon',
58
+ '.webp': 'image/webp',
59
+ '.woff': 'font/woff',
60
+ '.woff2': 'font/woff2',
61
+ '.ttf': 'font/ttf',
62
+ '.eot': 'application/vnd.ms-fontobject',
63
+ };
64
+ return mimeTypes[ext] || 'application/octet-stream';
65
+ }
66
+ /**
67
+ * Serves files from the dist directory
68
+ */
69
+ async function serveFile(requestPath) {
70
+ let filePath = join(outDir, requestPath === '/' ? 'index.html' : requestPath);
71
+ try {
72
+ const stats = await stat(filePath);
73
+ if (stats.isDirectory()) {
74
+ // Try to serve index.html from directory
75
+ const indexPath = join(filePath, 'index.html');
76
+ try {
77
+ await stat(indexPath);
78
+ filePath = indexPath;
79
+ }
80
+ catch {
81
+ return {
82
+ content: '404 - Directory listing not available',
83
+ mimeType: 'text/plain',
84
+ statusCode: 404,
85
+ };
86
+ }
87
+ }
88
+ const mimeType = getMimeType(filePath);
89
+ const content = await readFile(filePath);
90
+ // Unlike dev server, we don't inject live reload script in preview mode
91
+ return {
92
+ content,
93
+ mimeType,
94
+ statusCode: 200,
95
+ };
96
+ }
97
+ catch {
98
+ // File not found
99
+ return {
100
+ content: '404 - File not found',
101
+ mimeType: 'text/plain',
102
+ statusCode: 404,
103
+ };
104
+ }
105
+ }
106
+ const previewServer = {
107
+ url,
108
+ async start() {
109
+ // Create HTTP server
110
+ httpServer = createServer(async (req, res) => {
111
+ const requestPath = req.url || '/';
112
+ logger.processing?.(`${req.method} ${requestPath}`);
113
+ try {
114
+ const { content, mimeType, statusCode } = await serveFile(requestPath);
115
+ res.writeHead(statusCode, {
116
+ 'Content-Type': mimeType,
117
+ 'Access-Control-Allow-Origin': '*',
118
+ 'Cache-Control': 'public, max-age=31536000', // Better caching for production preview
119
+ });
120
+ res.end(content);
121
+ }
122
+ catch (error) {
123
+ logger.error?.(`Server error: ${error instanceof Error ? error.message : String(error)}`);
124
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
125
+ res.end('500 - Internal Server Error');
126
+ }
127
+ });
128
+ // Start HTTP server
129
+ await new Promise((resolve, reject) => {
130
+ httpServer.listen(port, host, () => {
131
+ resolve();
132
+ });
133
+ httpServer.on('error', (error) => {
134
+ reject(error);
135
+ });
136
+ });
137
+ logger.success?.(`Preview server running at ${url}`);
138
+ logger.info?.(`\nServing from:`);
139
+ logger.info?.(` 📁 ${outDir}`);
140
+ // Open browser if requested
141
+ if (open) {
142
+ try {
143
+ const { default: openBrowser } = await import('open');
144
+ await openBrowser(url);
145
+ }
146
+ catch {
147
+ logger.info?.('Could not open browser automatically');
148
+ }
149
+ }
150
+ },
151
+ async stop() {
152
+ if (httpServer) {
153
+ await new Promise((resolve) => {
154
+ httpServer.close(() => {
155
+ resolve();
156
+ });
157
+ });
158
+ httpServer = null;
159
+ }
160
+ },
161
+ };
162
+ return previewServer;
163
+ }
@@ -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;AAiLzF,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,5 +1,5 @@
1
1
  import { Eta } from 'eta';
2
- import { join, dirname, relative, basename } from 'path';
2
+ import { join, dirname, relative, basename, posix } from 'path';
3
3
  import glob from 'fast-glob';
4
4
  import { TEMPLATE_EXTENSION } from '../constants.js';
5
5
  import { isCollectionIndexPage, discoverLayout, getCollectionPathForPage, } from './utils/template-discovery.js';
@@ -146,7 +146,7 @@ async function discoverPartials(pagePath, config) {
146
146
  const fullPath = join(folderPath, etaFile);
147
147
  // Get relative path from srcDir to the partial file
148
148
  const relativePath = relative(srcDir, fullPath);
149
- partials[partialName] = relativePath;
149
+ partials[partialName] = posix.normalize(relativePath);
150
150
  }
151
151
  }
152
152
  }
@@ -157,6 +157,7 @@ export function createTemplateEngine(config) {
157
157
  const eta = new Eta({
158
158
  views: templateDir,
159
159
  cache: process.env.NODE_ENV === 'production',
160
+ cacheFilepaths: process.env.NODE_ENV === 'production',
160
161
  });
161
162
  return eta;
162
163
  }
package/dist/index.d.ts CHANGED
@@ -22,9 +22,11 @@
22
22
  export type { StatiConfig, PageModel, FrontMatter, BuildContext, PageContext, BuildHooks, NavNode, ISGConfig, AgingRule, BuildStats, } from './types/index.js';
23
23
  export type { BuildOptions } from './core/build.js';
24
24
  export type { DevServerOptions } from './core/dev.js';
25
+ export type { PreviewServerOptions } from './core/preview.js';
25
26
  export type { InvalidationResult } from './core/invalidate.js';
26
27
  export { build } from './core/build.js';
27
28
  export { createDevServer } from './core/dev.js';
29
+ export { createPreviewServer } from './core/preview.js';
28
30
  export { loadConfig } from './config/loader.js';
29
31
  export { invalidate } from './core/invalidate.js';
30
32
  import type { StatiConfig } from './types/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,YAAY,EACV,WAAW,EACX,SAAS,EACT,WAAW,EACX,YAAY,EACZ,WAAW,EACX,UAAU,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,UAAU,GACX,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGlD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAE7D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,YAAY,EACV,WAAW,EACX,SAAS,EACT,WAAW,EACX,YAAY,EACZ,WAAW,EACX,UAAU,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,UAAU,GACX,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGlD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAE7D"}
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@
21
21
  */
22
22
  export { build } from './core/build.js';
23
23
  export { createDevServer } from './core/dev.js';
24
+ export { createPreviewServer } from './core/preview.js';
24
25
  export { loadConfig } from './config/loader.js';
25
26
  export { invalidate } from './core/invalidate.js';
26
27
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stati/core",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",