@stati/core 1.13.0 → 1.14.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.
@@ -9,7 +9,7 @@ import { loadCacheManifest, saveCacheManifest, shouldRebuildPage, createCacheEnt
9
9
  import { generateSitemap, generateRobotsTxtFromConfig, autoInjectSEO, } from '../seo/index.js';
10
10
  import { generateRSSFeeds, validateRSSConfig } from '../rss/index.js';
11
11
  import { getEnv } from '../env.js';
12
- import { DEFAULT_TS_OUT_DIR } from '../constants.js';
12
+ import { DEFAULT_TS_OUT_DIR, DEFAULT_OUT_DIR } from '../constants.js';
13
13
  /**
14
14
  * Recursively calculates the total size of a directory in bytes.
15
15
  * Used for build statistics.
@@ -244,23 +244,12 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
244
244
  // Cache miss - need to rebuild
245
245
  cacheMisses++;
246
246
  const startTime = Date.now();
247
- // Add rendering substeps to tree
248
- const markdownId = `${pageId}-markdown`;
249
- const templateId = `${pageId}-template`;
250
- if (logger.addTreeNode) {
251
- logger.addTreeNode(pageId, markdownId, 'Processing Markdown', 'running');
252
- logger.addTreeNode(pageId, templateId, 'Applying Template', 'pending');
253
- }
254
247
  // Run beforeRender hook
255
248
  if (config.hooks?.beforeRender) {
256
249
  await config.hooks.beforeRender({ page, config });
257
250
  }
258
251
  // Render markdown to HTML
259
252
  const htmlContent = renderMarkdown(page.content, md);
260
- if (logger.updateTreeNode) {
261
- logger.updateTreeNode(markdownId, 'completed');
262
- logger.updateTreeNode(templateId, 'running');
263
- }
264
253
  // Render with template
265
254
  let finalHtml = await renderPage(page, htmlContent, config, eta, navigation, pages, assets);
266
255
  // Auto-inject SEO tags if enabled
@@ -282,7 +271,6 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
282
271
  }
283
272
  const renderTime = Date.now() - startTime;
284
273
  if (logger.updateTreeNode) {
285
- logger.updateTreeNode(templateId, 'completed');
286
274
  logger.updateTreeNode(pageId, 'completed', {
287
275
  timing: renderTime,
288
276
  url: page.url,
@@ -407,7 +395,7 @@ async function buildInternal(options = {}) {
407
395
  tsResult = await compileTypeScript({
408
396
  projectRoot: process.cwd(),
409
397
  config: config.typescript,
410
- outDir: config.outDir || 'dist',
398
+ outDir: config.outDir || DEFAULT_OUT_DIR,
411
399
  mode: getEnv() === 'production' ? 'production' : 'development',
412
400
  logger,
413
401
  });
@@ -432,6 +420,7 @@ async function buildInternal(options = {}) {
432
420
  const inventorySize = getInventorySize();
433
421
  if (inventorySize > 0) {
434
422
  await writeTailwindClassInventory(cacheDir);
423
+ logger.info('');
435
424
  logger.info(`📝 Generated Tailwind class inventory (${inventorySize} classes tracked)`);
436
425
  }
437
426
  // Disable inventory tracking after build
@@ -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;AAsB7D,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;AA4SD,wBAAsB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC,CAwaxF"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/core/dev.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,mBAAmB,CAAC;AA2B7D,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;AA4SD,wBAAsB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC,CAwaxF"}
package/dist/core/dev.js CHANGED
@@ -11,7 +11,7 @@ import { loadContent } from './content.js';
11
11
  import { buildNavigation } from './navigation.js';
12
12
  import { resolveDevPaths, resolveCacheDir, resolvePrettyUrl, createErrorOverlay, parseErrorDetails, TemplateError, createFallbackLogger, mergeServerOptions, createTypeScriptWatcher, } from './utils/index.js';
13
13
  import { setEnv, getEnv } from '../env.js';
14
- import { DEFAULT_DEV_PORT, DEFAULT_DEV_HOST, TEMPLATE_EXTENSION } from '../constants.js';
14
+ import { DEFAULT_DEV_PORT, DEFAULT_DEV_HOST, TEMPLATE_EXTENSION, DEFAULT_OUT_DIR, } from '../constants.js';
15
15
  /**
16
16
  * Loads and validates configuration for the dev server.
17
17
  */
@@ -527,11 +527,11 @@ export async function createDevServer(options = {}) {
527
527
  tsWatcher = await createTypeScriptWatcher({
528
528
  projectRoot: process.cwd(),
529
529
  config: config.typescript,
530
- outDir: config.outDir || 'dist',
530
+ outDir: config.outDir || DEFAULT_OUT_DIR,
531
531
  mode: 'development',
532
532
  logger,
533
- onRebuild: () => {
534
- logger.info?.('TypeScript recompiled, triggering reload...');
533
+ onRebuild: (bundlePath, compileTimeMs) => {
534
+ logger.info?.(`⚡ ${bundlePath} recompiled in ${compileTimeMs}ms`);
535
535
  // Broadcast reload to WebSocket clients
536
536
  if (wsServer) {
537
537
  wsServer.clients.forEach((client) => {
@@ -31,9 +31,19 @@ export interface CompileResult {
31
31
  /**
32
32
  * Options for TypeScript file watcher.
33
33
  */
34
- export interface WatchOptions extends CompileOptions {
35
- /** Callback invoked when files are recompiled */
36
- onRebuild: () => void;
34
+ export interface WatchOptions {
35
+ /** Project root directory */
36
+ projectRoot: string;
37
+ /** TypeScript configuration */
38
+ config: TypeScriptConfig;
39
+ /** Output directory for the build */
40
+ outDir?: string;
41
+ /** Build mode */
42
+ mode: 'development' | 'production';
43
+ /** Logger instance */
44
+ logger: Logger;
45
+ /** Callback when rebuild completes, receives bundle path and compile time in ms */
46
+ onRebuild: (bundlePath: string, compileTimeMs: number) => void;
37
47
  }
38
48
  /**
39
49
  * Compile TypeScript files using esbuild.
@@ -1 +1 @@
1
- {"version":3,"file":"typescript.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/typescript.utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAInC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAQrD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,0GAA0G;IAC1G,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,iDAAiD;IACjD,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAiCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA8CvF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAiD/B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgB5E;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAgCzE"}
1
+ {"version":3,"file":"typescript.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/typescript.utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAInC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AASrD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,0GAA0G;IAC1G,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,6BAA6B;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iBAAiB;IACjB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,SAAS,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;CAChE;AAiCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA+CvF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAsD/B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgB5E;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAsCzE"}
@@ -7,7 +7,7 @@ import * as esbuild from 'esbuild';
7
7
  import * as path from 'node:path';
8
8
  import * as fs from 'node:fs/promises';
9
9
  import { pathExists } from './fs.utils.js';
10
- import { DEFAULT_TS_SRC_DIR, DEFAULT_TS_OUT_DIR, DEFAULT_TS_ENTRY_POINT, DEFAULT_TS_BUNDLE_NAME, } from '../../constants.js';
10
+ import { DEFAULT_TS_SRC_DIR, DEFAULT_TS_OUT_DIR, DEFAULT_TS_ENTRY_POINT, DEFAULT_TS_BUNDLE_NAME, DEFAULT_OUT_DIR, } from '../../constants.js';
11
11
  function resolveConfig(config, mode) {
12
12
  const isProduction = mode === 'production';
13
13
  return {
@@ -46,13 +46,14 @@ export async function compileTypeScript(options) {
46
46
  const resolved = resolveConfig(config, mode);
47
47
  const entryPath = path.join(projectRoot, resolved.srcDir, resolved.entryPoint);
48
48
  // Output to configured build output directory (default: dist)
49
- const outDir = path.join(projectRoot, globalOutDir || 'dist', resolved.outDir);
49
+ const outDir = path.join(projectRoot, globalOutDir || DEFAULT_OUT_DIR, resolved.outDir);
50
50
  // Validate entry point exists
51
51
  if (!(await pathExists(entryPath))) {
52
52
  logger.warning(`TypeScript entry point not found: ${entryPath}`);
53
53
  logger.warning('Skipping TypeScript compilation.');
54
54
  return {};
55
55
  }
56
+ logger.info(''); // Add empty line before TypeScript compilation
56
57
  logger.info('Compiling TypeScript...');
57
58
  try {
58
59
  const result = await esbuild.build({
@@ -108,7 +109,7 @@ export async function createTypeScriptWatcher(options) {
108
109
  const resolved = resolveConfig(config, mode);
109
110
  const entryPath = path.join(projectRoot, resolved.srcDir, resolved.entryPoint);
110
111
  // Output to configured build output directory (default: dist)
111
- const outDir = path.join(projectRoot, globalOutDir || 'dist', resolved.outDir);
112
+ const outDir = path.join(projectRoot, globalOutDir || DEFAULT_OUT_DIR, resolved.outDir);
112
113
  // Validate entry point exists
113
114
  if (!(await pathExists(entryPath))) {
114
115
  logger.warning(`TypeScript entry point not found: ${entryPath}`);
@@ -129,15 +130,20 @@ export async function createTypeScriptWatcher(options) {
129
130
  {
130
131
  name: 'stati-rebuild-notify',
131
132
  setup(build) {
133
+ let startTime;
134
+ build.onStart(() => {
135
+ startTime = Date.now();
136
+ });
132
137
  build.onEnd((result) => {
138
+ const compileTime = Date.now() - startTime;
133
139
  if (result.errors.length > 0) {
134
140
  result.errors.forEach((err) => {
135
141
  logger.error(`TypeScript error: ${err.text}`);
136
142
  });
137
143
  }
138
144
  else {
139
- logger.info('TypeScript recompiled.');
140
- onRebuild();
145
+ const bundlePath = `${globalOutDir || DEFAULT_OUT_DIR}/${resolved.outDir}/${resolved.bundleName}.js`;
146
+ onRebuild(bundlePath, compileTime);
141
147
  }
142
148
  });
143
149
  },
@@ -219,8 +225,11 @@ export function autoInjectBundle(html, bundlePath) {
219
225
  // Invalid path format, skip injection to prevent potential XSS
220
226
  return html;
221
227
  }
222
- // Check if the bundle is already included (avoid duplicate injection)
223
- if (html.includes(bundlePath)) {
228
+ // Check if the bundle script tag is already included (avoid duplicate injection)
229
+ // Must check for actual script tag, not just any occurrence of the path
230
+ // (e.g., modulepreload links should not prevent script injection)
231
+ const scriptTagPattern = new RegExp(`<script[^>]*\\ssrc=["']${bundlePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}["'][^>]*>`, 'i');
232
+ if (scriptTagPattern.test(html)) {
224
233
  return html;
225
234
  }
226
235
  // Find </body> tag (case-insensitive)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stati/core",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",