@stati/core 1.6.4 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +1 -1
  2. package/dist/core/build.d.ts.map +1 -1
  3. package/dist/core/build.js +42 -6
  4. package/dist/core/content.d.ts.map +1 -1
  5. package/dist/core/content.js +1 -2
  6. package/dist/core/dev.d.ts.map +1 -1
  7. package/dist/core/dev.js +2 -5
  8. package/dist/core/index.d.ts +13 -0
  9. package/dist/core/index.d.ts.map +1 -0
  10. package/dist/core/index.js +12 -0
  11. package/dist/core/invalidate.js +2 -2
  12. package/dist/core/isg/build-lock.js +1 -1
  13. package/dist/core/isg/deps.d.ts.map +1 -1
  14. package/dist/core/isg/deps.js +1 -3
  15. package/dist/core/isg/hash.js +1 -1
  16. package/dist/core/isg/index.d.ts +16 -0
  17. package/dist/core/isg/index.d.ts.map +1 -0
  18. package/dist/core/isg/index.js +22 -0
  19. package/dist/core/isg/manifest.js +1 -1
  20. package/dist/core/preview.d.ts.map +1 -1
  21. package/dist/core/preview.js +1 -2
  22. package/dist/core/templates.d.ts.map +1 -1
  23. package/dist/core/templates.js +4 -7
  24. package/dist/core/utils/index.d.ts +16 -0
  25. package/dist/core/utils/index.d.ts.map +1 -0
  26. package/dist/core/utils/index.js +22 -0
  27. package/dist/core/utils/partial-validation.d.ts.map +1 -1
  28. package/dist/core/utils/partial-validation.js +2 -1
  29. package/dist/index.d.ts +6 -8
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +3 -4
  32. package/dist/seo/auto-inject.d.ts +48 -0
  33. package/dist/seo/auto-inject.d.ts.map +1 -0
  34. package/dist/seo/auto-inject.js +108 -0
  35. package/dist/seo/generator.d.ts +77 -0
  36. package/dist/seo/generator.d.ts.map +1 -0
  37. package/dist/seo/generator.js +320 -0
  38. package/dist/seo/index.d.ts +12 -0
  39. package/dist/seo/index.d.ts.map +1 -0
  40. package/dist/seo/index.js +15 -0
  41. package/dist/seo/robots.d.ts +84 -0
  42. package/dist/seo/robots.d.ts.map +1 -0
  43. package/dist/seo/robots.js +165 -0
  44. package/dist/seo/sitemap.d.ts +37 -0
  45. package/dist/seo/sitemap.d.ts.map +1 -0
  46. package/dist/seo/sitemap.js +320 -0
  47. package/dist/seo/utils/escape-and-validation.d.ts +99 -0
  48. package/dist/seo/utils/escape-and-validation.d.ts.map +1 -0
  49. package/dist/seo/utils/escape-and-validation.js +319 -0
  50. package/dist/seo/utils/index.d.ts +7 -0
  51. package/dist/seo/utils/index.d.ts.map +1 -0
  52. package/dist/seo/utils/index.js +8 -0
  53. package/dist/seo/utils/url.d.ts +46 -0
  54. package/dist/seo/utils/url.d.ts.map +1 -0
  55. package/dist/seo/utils/url.js +66 -0
  56. package/dist/seo/utils.d.ts +94 -0
  57. package/dist/seo/utils.d.ts.map +1 -0
  58. package/dist/seo/utils.js +304 -0
  59. package/dist/types/config.d.ts +58 -0
  60. package/dist/types/config.d.ts.map +1 -1
  61. package/dist/types/content.d.ts +181 -0
  62. package/dist/types/content.d.ts.map +1 -1
  63. package/dist/types/index.d.ts +5 -2
  64. package/dist/types/index.d.ts.map +1 -1
  65. package/dist/types/index.js +1 -1
  66. package/dist/types/seo.d.ts +69 -0
  67. package/dist/types/seo.d.ts.map +1 -0
  68. package/dist/types/seo.js +36 -0
  69. package/dist/types/sitemap.d.ts +94 -0
  70. package/dist/types/sitemap.d.ts.map +1 -0
  71. package/dist/types/sitemap.js +4 -0
  72. package/package.json +1 -1
  73. package/dist/core/utils/partials.d.ts +0 -24
  74. package/dist/core/utils/partials.d.ts.map +0 -1
  75. package/dist/core/utils/partials.js +0 -85
  76. package/dist/tests/utils/test-mocks.d.ts +0 -69
  77. package/dist/tests/utils/test-mocks.d.ts.map +0 -1
  78. package/dist/tests/utils/test-mocks.js +0 -125
  79. package/dist/types.d.ts +0 -543
  80. package/dist/types.d.ts.map +0 -1
  81. package/dist/types.js +0 -1
package/README.md CHANGED
@@ -266,4 +266,4 @@ Stati Core is built with a modular architecture:
266
266
 
267
267
  ## License
268
268
 
269
- MIT
269
+ MIT © [Imre Csige](https://github.com/ianchak)
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/core/build.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAEV,UAAU,EACV,MAAM,EAKP,MAAM,mBAAmB,CAAC;AAG3B;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA4FD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CAW3E"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/core/build.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAEV,UAAU,EACV,MAAM,EAKP,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA4FD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CAW3E"}
@@ -1,4 +1,4 @@
1
- import { ensureDir, writeFile, remove, pathExists, stat, readdir, copyFile } from './utils/fs.js';
1
+ import { ensureDir, writeFile, remove, pathExists, stat, readdir, copyFile, resolveOutDir, resolveStaticDir, resolveCacheDir, } from './utils/index.js';
2
2
  import { join, dirname, relative } from 'path';
3
3
  import { posix } from 'path';
4
4
  import { loadConfig } from '../config/loader.js';
@@ -6,10 +6,8 @@ import { loadContent } from './content.js';
6
6
  import { createMarkdownProcessor, renderMarkdown } from './markdown.js';
7
7
  import { createTemplateEngine, renderPage } from './templates.js';
8
8
  import { buildNavigation } from './navigation.js';
9
- import { loadCacheManifest, saveCacheManifest } from './isg/manifest.js';
10
- import { shouldRebuildPage, createCacheEntry, updateCacheEntry } from './isg/builder.js';
11
- import { withBuildLock } from './isg/build-lock.js';
12
- import { resolveOutDir, resolveStaticDir, resolveCacheDir } from './utils/paths.js';
9
+ import { loadCacheManifest, saveCacheManifest, shouldRebuildPage, createCacheEntry, updateCacheEntry, withBuildLock, } from './isg/index.js';
10
+ import { generateSitemap, generateRobotsTxtFromConfig, autoInjectSEO, } from '../seo/index.js';
13
11
  /**
14
12
  * Recursively calculates the total size of a directory in bytes.
15
13
  * Used for build statistics.
@@ -260,7 +258,20 @@ async function processPagesWithCache(pages, manifest, config, outDir, md, eta, n
260
258
  logger.updateTreeNode(templateId, 'running');
261
259
  }
262
260
  // Render with template
263
- const finalHtml = await renderPage(page, htmlContent, config, eta, navigation, pages);
261
+ let finalHtml = await renderPage(page, htmlContent, config, eta, navigation, pages);
262
+ // Auto-inject SEO tags if enabled
263
+ if (config.seo?.autoInject !== false) {
264
+ const injectOptions = {
265
+ page,
266
+ config,
267
+ siteUrl: config.site.baseUrl,
268
+ logger,
269
+ };
270
+ if (config.seo?.debug !== undefined) {
271
+ injectOptions.debug = config.seo.debug;
272
+ }
273
+ finalHtml = autoInjectSEO(finalHtml, injectOptions);
274
+ }
264
275
  const renderTime = Date.now() - startTime;
265
276
  if (logger.updateTreeNode) {
266
277
  logger.updateTreeNode(templateId, 'completed');
@@ -367,6 +378,31 @@ async function buildInternal(options = {}) {
367
378
  // Copy static assets and count them
368
379
  let assetsCount = 0;
369
380
  assetsCount = await copyStaticAssets(config, outDir, logger);
381
+ // Generate sitemap if enabled
382
+ if (config.sitemap?.enabled) {
383
+ console.log(); // Add spacing before sitemap generation
384
+ logger.info('Generating sitemap...');
385
+ const sitemapResult = generateSitemap(pages, config, config.sitemap);
386
+ await writeFile(join(outDir, 'sitemap.xml'), sitemapResult.xml);
387
+ // Write additional sitemap files if split
388
+ if (sitemapResult.sitemaps) {
389
+ for (const { filename, xml } of sitemapResult.sitemaps) {
390
+ await writeFile(join(outDir, filename), xml);
391
+ }
392
+ logger.success(`Generated sitemap index with ${sitemapResult.sitemaps.length} sitemaps (${sitemapResult.entryCount} entries)`);
393
+ }
394
+ else {
395
+ logger.success(`Generated sitemap with ${sitemapResult.entryCount} entries`);
396
+ }
397
+ }
398
+ // Generate robots.txt if enabled
399
+ if (config.robots?.enabled) {
400
+ console.log(); // Add spacing before robots.txt generation
401
+ logger.info('Generating robots.txt...');
402
+ const robotsContent = generateRobotsTxtFromConfig(config.robots, config.site.baseUrl);
403
+ await writeFile(join(outDir, 'robots.txt'), robotsContent);
404
+ logger.success('Generated robots.txt');
405
+ }
370
406
  // Run afterAll hook
371
407
  if (config.hooks?.afterAll) {
372
408
  await config.hooks.afterAll({ config, pages });
@@ -1 +1 @@
1
- {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/core/content.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIhE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,aAAa,CAAC,EAAE,OAAO,GACtB,OAAO,CAAC,SAAS,EAAE,CAAC,CA6CtB"}
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/core/content.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,aAAa,CAAC,EAAE,OAAO,GACtB,OAAO,CAAC,SAAS,EAAE,CAAC,CA6CtB"}
@@ -1,9 +1,8 @@
1
1
  import glob from 'fast-glob';
2
- import { readFile } from './utils/fs.js';
2
+ import { readFile, resolveSrcDir } from './utils/index.js';
3
3
  import matter from 'gray-matter';
4
4
  import { relative, dirname, basename } from 'path';
5
5
  import { MARKDOWN_EXTENSION } from '../constants.js';
6
- import { resolveSrcDir } from './utils/paths.js';
7
6
  /**
8
7
  * Loads and parses all content files from the configured source directory.
9
8
  *
@@ -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;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"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/core/dev.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAiB7D,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
@@ -7,11 +7,8 @@ import chokidar from 'chokidar';
7
7
  import { build } from './build.js';
8
8
  import { invalidate } from './invalidate.js';
9
9
  import { loadConfig } from '../config/loader.js';
10
- import { loadCacheManifest, saveCacheManifest } from './isg/manifest.js';
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';
10
+ import { loadCacheManifest, saveCacheManifest } from './isg/index.js';
11
+ import { resolveDevPaths, resolveCacheDir, resolvePrettyUrl, createErrorOverlay, parseErrorDetails, TemplateError, } from './utils/index.js';
15
12
  import { setEnv, getEnv } from '../env.js';
16
13
  import { DEFAULT_DEV_PORT, DEFAULT_DEV_HOST, TEMPLATE_EXTENSION } from '../constants.js';
17
14
  /**
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @fileoverview Core build engine exports
3
+ * Barrel file for all core Stati functionality including build, dev server, preview, and invalidation.
4
+ */
5
+ export { build } from './build.js';
6
+ export type { BuildOptions } from './build.js';
7
+ export { createDevServer } from './dev.js';
8
+ export type { DevServerOptions } from './dev.js';
9
+ export { createPreviewServer } from './preview.js';
10
+ export type { PreviewServerOptions } from './preview.js';
11
+ export { invalidate } from './invalidate.js';
12
+ export type { InvalidationResult } from './invalidate.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAGjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAGzD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @fileoverview Core build engine exports
3
+ * Barrel file for all core Stati functionality including build, dev server, preview, and invalidation.
4
+ */
5
+ // Build functionality
6
+ export { build } from './build.js';
7
+ // Development server
8
+ export { createDevServer } from './dev.js';
9
+ // Preview server
10
+ export { createPreviewServer } from './preview.js';
11
+ // Cache invalidation
12
+ export { invalidate } from './invalidate.js';
@@ -1,5 +1,5 @@
1
- import { loadCacheManifest, saveCacheManifest } from './isg/manifest.js';
2
- import { resolveCacheDir } from './utils/paths.js';
1
+ import { loadCacheManifest, saveCacheManifest } from './isg/index.js';
2
+ import { resolveCacheDir } from './utils/index.js';
3
3
  /**
4
4
  * Parses an invalidation query string into individual query terms.
5
5
  * Supports space-separated values and quoted strings.
@@ -1,4 +1,4 @@
1
- import { writeFile, readFile, pathExists, remove, ensureDir } from '../utils/fs.js';
1
+ import { writeFile, readFile, pathExists, remove, ensureDir } from '../utils/index.js';
2
2
  import { join, dirname } from 'path';
3
3
  import { hostname } from 'os';
4
4
  /**
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../../src/core/isg/deps.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnE;;GAEG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,eAAe,EAAE,MAAM,EAAE;gBAAzB,eAAe,EAAE,MAAM,EAAE,EACzC,OAAO,EAAE,MAAM;CAKlB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,EAAE,CAAC,CAoCnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,EAAE,CAAC,CAiDnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CASxB"}
@@ -1,9 +1,7 @@
1
1
  import { join, dirname, relative, posix } from 'path';
2
- import { pathExists, readFile } from '../utils/fs.js';
2
+ import { pathExists, readFile, isCollectionIndexPage, discoverLayout, resolveSrcDir, } from '../utils/index.js';
3
3
  import glob from 'fast-glob';
4
4
  import { TEMPLATE_EXTENSION } from '../../constants.js';
5
- import { isCollectionIndexPage, discoverLayout } from '../utils/template-discovery.js';
6
- import { resolveSrcDir } from '../utils/paths.js';
7
5
  /**
8
6
  * Error thrown when a circular dependency is detected in templates.
9
7
  */
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'crypto';
2
- import { readFile, pathExists } from '../utils/fs.js';
2
+ import { readFile, pathExists } from '../utils/index.js';
3
3
  /**
4
4
  * Creates a SHA-256 hash instance, updates it with data, and returns the hex digest.
5
5
  * Internal utility to eliminate duplicate hash creation patterns.
@@ -0,0 +1,16 @@
1
+ /**
2
+ * ISG (Incremental Static Generation) Module
3
+ *
4
+ * This module provides intelligent caching and incremental rebuild capabilities for Stati.
5
+ * It tracks page dependencies, computes content hashes, and manages TTL-based rebuild strategies.
6
+ *
7
+ * @module isg
8
+ */
9
+ export { loadCacheManifest, saveCacheManifest, createEmptyManifest } from './manifest.js';
10
+ export { shouldRebuildPage, createCacheEntry, updateCacheEntry } from './builder.js';
11
+ export { BuildLockManager, withBuildLock } from './build-lock.js';
12
+ export { CircularDependencyError, trackTemplateDependencies, findPartialDependencies, resolveTemplatePath, } from './deps.js';
13
+ export { computeContentHash, computeFileHash, computeInputsHash } from './hash.js';
14
+ export { getSafeCurrentTime, parseSafeDate, computeEffectiveTTL, computeNextRebuildAt, isPageFrozen, applyAgingRules, } from './ttl.js';
15
+ export { ISGConfigurationError, validateISGConfig, validatePageISGOverrides, extractNumericOverride, } from './validation.js';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/isg/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAG1F,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAGrF,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGlE,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAGnF,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,eAAe,GAChB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * ISG (Incremental Static Generation) Module
3
+ *
4
+ * This module provides intelligent caching and incremental rebuild capabilities for Stati.
5
+ * It tracks page dependencies, computes content hashes, and manages TTL-based rebuild strategies.
6
+ *
7
+ * @module isg
8
+ */
9
+ // Manifest management
10
+ export { loadCacheManifest, saveCacheManifest, createEmptyManifest } from './manifest.js';
11
+ // Build decision logic
12
+ export { shouldRebuildPage, createCacheEntry, updateCacheEntry } from './builder.js';
13
+ // Build locking
14
+ export { BuildLockManager, withBuildLock } from './build-lock.js';
15
+ // Dependency tracking
16
+ export { CircularDependencyError, trackTemplateDependencies, findPartialDependencies, resolveTemplatePath, } from './deps.js';
17
+ // Hash computation
18
+ export { computeContentHash, computeFileHash, computeInputsHash } from './hash.js';
19
+ // TTL and aging
20
+ export { getSafeCurrentTime, parseSafeDate, computeEffectiveTTL, computeNextRebuildAt, isPageFrozen, applyAgingRules, } from './ttl.js';
21
+ // Validation
22
+ export { ISGConfigurationError, validateISGConfig, validatePageISGOverrides, extractNumericOverride, } from './validation.js';
@@ -1,4 +1,4 @@
1
- import { readFile, writeFile, pathExists, ensureDir } from '../utils/fs.js';
1
+ import { readFile, writeFile, pathExists, ensureDir } from '../utils/index.js';
2
2
  import { join } from 'path';
3
3
  import { MANIFEST_FILENAME } from '../../constants.js';
4
4
  /**
@@ -1 +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,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0BAA0B;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sBAAsB;IACtB,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"}
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,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0BAA0B;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sBAAsB;IACtB,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"}
@@ -2,8 +2,7 @@ import { createServer } from 'http';
2
2
  import { join, extname } from 'path';
3
3
  import { readFile } from 'fs/promises';
4
4
  import { loadConfig } from '../config/loader.js';
5
- import { resolveDevPaths } from './utils/paths.js';
6
- import { resolvePrettyUrl } from './utils/server.js';
5
+ import { resolveDevPaths, resolvePrettyUrl } from './utils/index.js';
7
6
  import { DEFAULT_PREVIEW_PORT, DEFAULT_DEV_HOST } from '../constants.js';
8
7
  /**
9
8
  * Loads and validates configuration for the preview server.
@@ -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;AAsLzF,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,CA4JjB"}
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;AAuLzF,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,CA6JjB"}
@@ -2,13 +2,9 @@ import { Eta } from 'eta';
2
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
- import { getStatiVersion } from './utils/version.js';
5
+ import { getStatiVersion, isCollectionIndexPage, discoverLayout, getCollectionPathForPage, resolveSrcDir, createTemplateError, createValidatingPartialsProxy, propValue, } from './utils/index.js';
6
6
  import { getEnv } from '../env.js';
7
- import { isCollectionIndexPage, discoverLayout, getCollectionPathForPage, } from './utils/template-discovery.js';
8
- import { resolveSrcDir } from './utils/paths.js';
9
- import { createTemplateError } from './utils/template-errors.js';
10
- import { createValidatingPartialsProxy } from './utils/partial-validation.js';
11
- import { propValue } from './utils/template-utils.js';
7
+ import { generateSEO } from '../seo/index.js';
12
8
  /**
13
9
  * Groups pages by their tags for aggregation purposes.
14
10
  *
@@ -198,7 +194,8 @@ export async function renderPage(page, body, config, eta, navigation, allPages)
198
194
  generator: {
199
195
  version: getStatiVersion(),
200
196
  },
201
- // Template utilities
197
+ // Stati utilities object with helper functions
198
+ generateSEO: (tags) => generateSEO({ page, config, site: config.site }, tags),
202
199
  propValue,
203
200
  };
204
201
  // Render partials and store their content
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Core utilities index
3
+ * @module core/utils
4
+ */
5
+ export { readFile, writeFile, pathExists, ensureDir, remove, copyFile, readdir, stat, } from './fs.js';
6
+ export { resolveSrcDir, resolveOutDir, resolveStaticDir, resolveCacheDir, resolveDevPaths, normalizeTemplatePath, resolveSrcPath, resolveOutPath, resolveStaticPath, } from './paths.js';
7
+ export { discoverLayout, isCollectionIndexPage, getCollectionPathForPage, } from './template-discovery.js';
8
+ export { propValue } from './template-utils.js';
9
+ export { createValidatingPartialsProxy } from './partial-validation.js';
10
+ export { TemplateError, parseEtaError, createTemplateError } from './template-errors.js';
11
+ export { resolvePrettyUrl } from './server.js';
12
+ export type { PrettyUrlResult } from './server.js';
13
+ export { createErrorOverlay, parseErrorDetails } from './error-overlay.js';
14
+ export type { ErrorDetails } from './error-overlay.js';
15
+ export { getStatiVersion } from './version.js';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/utils/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACL,QAAQ,EACR,SAAS,EACT,UAAU,EACV,SAAS,EACT,MAAM,EACN,QAAQ,EACR,OAAO,EACP,IAAI,GACL,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGhD,OAAO,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;AAGxE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC3E,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Core utilities index
3
+ * @module core/utils
4
+ */
5
+ // File system utilities
6
+ export { readFile, writeFile, pathExists, ensureDir, remove, copyFile, readdir, stat, } from './fs.js';
7
+ // Path resolution utilities
8
+ export { resolveSrcDir, resolveOutDir, resolveStaticDir, resolveCacheDir, resolveDevPaths, normalizeTemplatePath, resolveSrcPath, resolveOutPath, resolveStaticPath, } from './paths.js';
9
+ // Template discovery utilities
10
+ export { discoverLayout, isCollectionIndexPage, getCollectionPathForPage, } from './template-discovery.js';
11
+ // Template utilities
12
+ export { propValue } from './template-utils.js';
13
+ // Partial validation utilities
14
+ export { createValidatingPartialsProxy } from './partial-validation.js';
15
+ // Template error utilities
16
+ export { TemplateError, parseEtaError, createTemplateError } from './template-errors.js';
17
+ // Server utilities
18
+ export { resolvePrettyUrl } from './server.js';
19
+ // Error overlay utilities
20
+ export { createErrorOverlay, parseErrorDetails } from './error-overlay.js';
21
+ // Version utilities
22
+ export { getStatiVersion } from './version.js';
@@ -1 +1 @@
1
- {"version":3,"file":"partial-validation.d.ts","sourceRoot":"","sources":["../../../src/core/utils/partial-validation.ts"],"names":[],"mappings":"AA4FA;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmDxB"}
1
+ {"version":3,"file":"partial-validation.d.ts","sourceRoot":"","sources":["../../../src/core/utils/partial-validation.ts"],"names":[],"mappings":"AA8FA;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmDxB"}
@@ -1,3 +1,4 @@
1
+ import { getEnv } from '../../env.js';
1
2
  /**
2
3
  * Creates inline error overlay HTML for missing partials
3
4
  */
@@ -88,7 +89,7 @@ function findSimilarPartialNames(targetName, availableNames) {
88
89
  export function createValidatingPartialsProxy(partials) {
89
90
  // In production, return partials as-is
90
91
  // Only skip validation if explicitly set to production
91
- if (process.env.NODE_ENV === 'production') {
92
+ if (getEnv() === 'production') {
92
93
  return partials;
93
94
  }
94
95
  return new Proxy(partials, {
package/dist/index.d.ts CHANGED
@@ -20,15 +20,13 @@
20
20
  * ```
21
21
  */
22
22
  export type { StatiConfig, PageModel, FrontMatter, BuildContext, PageContext, BuildHooks, NavNode, ISGConfig, AgingRule, BuildStats, } from './types/index.js';
23
- export type { BuildOptions } from './core/build.js';
24
- export type { DevServerOptions } from './core/dev.js';
25
- export type { PreviewServerOptions } from './core/preview.js';
26
- export type { InvalidationResult } from './core/invalidate.js';
27
- export { build } from './core/build.js';
28
- export { createDevServer } from './core/dev.js';
29
- export { createPreviewServer } from './core/preview.js';
23
+ export type { SEOMetadata, SEOConfig, SEOContext, SEOValidationResult, SEOTagType, RobotsConfig, OpenGraphConfig, OpenGraphImage, OpenGraphArticle, TwitterCardConfig, AuthorConfig, } from './types/index.js';
24
+ export type { SitemapConfig, SitemapEntry, SitemapGenerationResult, ChangeFrequency, } from './types/index.js';
25
+ export type { BuildOptions, DevServerOptions, PreviewServerOptions, InvalidationResult, } from './core/index.js';
26
+ export { build, createDevServer, createPreviewServer, invalidate } from './core/index.js';
27
+ export type { AutoInjectOptions } from './seo/index.js';
28
+ export { generateSEOMetadata, generateSEO, generateOpenGraphTags, generateTwitterCardTags, generateSitemap, generateSitemapEntry, generateSitemapXml, generateSitemapIndexXml, generateRobotsTxt, generateRobotsTxtFromConfig, escapeHtml, generateRobotsContent, validateSEOMetadata, detectExistingSEOTags, normalizeUrlPath, resolveAbsoluteUrl, isValidUrl, autoInjectSEO, shouldAutoInject, } from './seo/index.js';
30
29
  export { loadConfig } from './config/loader.js';
31
- export { invalidate } from './core/invalidate.js';
32
30
  export { setEnv, getEnv } from './env.js';
33
31
  import type { StatiConfig } from './types/index.js';
34
32
  /**
@@ -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,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;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAG1C,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;AAG1B,YAAY,EACV,WAAW,EACX,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,UAAU,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,GACb,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,aAAa,EACb,YAAY,EACZ,uBAAuB,EACvB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG1F,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,2BAA2B,EAC3B,UAAU,EACV,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,aAAa,EACb,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAG1C,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
@@ -19,11 +19,10 @@
19
19
  * await build({ clean: true });
20
20
  * ```
21
21
  */
22
- export { build } from './core/build.js';
23
- export { createDevServer } from './core/dev.js';
24
- export { createPreviewServer } from './core/preview.js';
22
+ export { build, createDevServer, createPreviewServer, invalidate } from './core/index.js';
23
+ export { generateSEOMetadata, generateSEO, generateOpenGraphTags, generateTwitterCardTags, generateSitemap, generateSitemapEntry, generateSitemapXml, generateSitemapIndexXml, generateRobotsTxt, generateRobotsTxtFromConfig, escapeHtml, generateRobotsContent, validateSEOMetadata, detectExistingSEOTags, normalizeUrlPath, resolveAbsoluteUrl, isValidUrl, autoInjectSEO, shouldAutoInject, } from './seo/index.js';
24
+ // Re-export config and env utilities
25
25
  export { loadConfig } from './config/loader.js';
26
- export { invalidate } from './core/invalidate.js';
27
26
  export { setEnv, getEnv } from './env.js';
28
27
  /**
29
28
  * Helper function for defining Stati configuration with TypeScript IntelliSense.
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Automatic SEO tag injection utilities
3
+ * @module seo/auto-inject
4
+ */
5
+ import type { PageModel } from '../types/content.js';
6
+ import type { StatiConfig } from '../types/config.js';
7
+ import type { Logger } from '../types/logging.js';
8
+ /**
9
+ * Options for auto-injection
10
+ */
11
+ export interface AutoInjectOptions {
12
+ /** Page model with frontmatter and metadata */
13
+ page: PageModel;
14
+ /** Site configuration */
15
+ config: StatiConfig;
16
+ /** Site base URL */
17
+ siteUrl: string;
18
+ /** Logger for debug output */
19
+ logger: Logger;
20
+ /** Enable debug logging */
21
+ debug?: boolean;
22
+ }
23
+ /**
24
+ * Automatically injects SEO metadata into HTML if not already present
25
+ * @param html - Rendered HTML content
26
+ * @param options - Auto-injection options
27
+ * @returns HTML with injected SEO tags
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const html = '<html><head><title>Page</title></head><body>Content</body></html>';
32
+ * const enhanced = autoInjectSEO(html, {
33
+ * page: pageModel,
34
+ * config: statiConfig,
35
+ * siteUrl: 'https://example.com'
36
+ * });
37
+ * // Returns HTML with additional SEO meta tags injected
38
+ * ```
39
+ */
40
+ export declare function autoInjectSEO(html: string, options: AutoInjectOptions): string;
41
+ /**
42
+ * Checks if auto-injection is enabled for a page
43
+ * @param config - Site configuration
44
+ * @param _page - Page model (reserved for future page-level overrides)
45
+ * @returns true if auto-injection should run
46
+ */
47
+ export declare function shouldAutoInject(config: StatiConfig, _page: PageModel): boolean;
48
+ //# sourceMappingURL=auto-inject.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-inject.d.ts","sourceRoot":"","sources":["../../src/seo/auto-inject.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAIlD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,+CAA+C;IAC/C,IAAI,EAAE,SAAS,CAAC;IAChB,yBAAyB;IACzB,MAAM,EAAE,WAAW,CAAC;IACpB,oBAAoB;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA8BD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAgE9E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAK/E"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Automatic SEO tag injection utilities
3
+ * @module seo/auto-inject
4
+ */
5
+ import { detectExistingSEOTags } from './utils/index.js';
6
+ import { generateSEOMetadata } from './generator.js';
7
+ /**
8
+ * Helper function to conditionally log debug messages for SEO auto-injection.
9
+ * Checks both the explicit debug flag and the config-level debug setting.
10
+ *
11
+ * @param message - Debug message to log
12
+ * @param options - Object containing debug flag, config, and logger
13
+ */
14
+ function logDebug(message, options) {
15
+ if (options.debug || options.config.seo?.debug) {
16
+ const logMessage = `[SEO Auto-Inject] ${message}`;
17
+ options.logger.warning(logMessage);
18
+ }
19
+ }
20
+ /**
21
+ * Finds the position to inject SEO tags (before </head>)
22
+ * @param html - HTML content
23
+ * @returns Position index or -1 if not found
24
+ */
25
+ function findHeadClosePosition(html) {
26
+ // Case-insensitive search for </head>
27
+ const match = html.match(/<\/head>/i);
28
+ return match ? (match.index ?? -1) : -1;
29
+ }
30
+ /**
31
+ * Automatically injects SEO metadata into HTML if not already present
32
+ * @param html - Rendered HTML content
33
+ * @param options - Auto-injection options
34
+ * @returns HTML with injected SEO tags
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const html = '<html><head><title>Page</title></head><body>Content</body></html>';
39
+ * const enhanced = autoInjectSEO(html, {
40
+ * page: pageModel,
41
+ * config: statiConfig,
42
+ * siteUrl: 'https://example.com'
43
+ * });
44
+ * // Returns HTML with additional SEO meta tags injected
45
+ * ```
46
+ */
47
+ export function autoInjectSEO(html, options) {
48
+ const { page, config, siteUrl, debug, logger } = options;
49
+ // Check if auto-injection is enabled (default: true)
50
+ const autoInjectEnabled = config.seo?.autoInject !== false;
51
+ if (!autoInjectEnabled) {
52
+ logDebug(`Skipped for ${page.url} (disabled in config)`, { debug, config, logger });
53
+ return html;
54
+ }
55
+ // Detect existing SEO tags in the HTML
56
+ const existingTags = detectExistingSEOTags(html);
57
+ logDebug(`Existing tags in ${page.url}: ${Array.from(existingTags).join(', ')}`, {
58
+ debug,
59
+ config,
60
+ logger,
61
+ });
62
+ // Build context with optional exclude parameter and logger
63
+ const context = {
64
+ page,
65
+ config,
66
+ siteUrl,
67
+ logger,
68
+ };
69
+ // Only add exclude if we have existing tags
70
+ if (existingTags.size > 0) {
71
+ context.exclude = existingTags;
72
+ }
73
+ // Generate SEO metadata excluding existing tags
74
+ const seoMetadata = generateSEOMetadata(context);
75
+ // If no SEO metadata was generated (all tags exist), return original HTML
76
+ if (!seoMetadata || seoMetadata.trim().length === 0) {
77
+ logDebug(`No tags to inject for ${page.url} (all exist)`, { debug, config, logger });
78
+ return html;
79
+ }
80
+ // Find position to inject (before </head>)
81
+ const headClosePos = findHeadClosePosition(html);
82
+ if (headClosePos === -1) {
83
+ logDebug(`No </head> tag found in ${page.url}, skipping injection`, { debug, config, logger });
84
+ return html;
85
+ }
86
+ // Inject SEO metadata before </head>
87
+ const before = html.substring(0, headClosePos);
88
+ const after = html.substring(headClosePos);
89
+ // Add proper indentation (2 spaces) and newline
90
+ const injected = `${before} ${seoMetadata}\n${after}`;
91
+ logDebug(`Injected ${existingTags.size === 0 ? 'all' : 'missing'} SEO tags into ${page.url}`, {
92
+ debug,
93
+ config,
94
+ logger,
95
+ });
96
+ return injected;
97
+ }
98
+ /**
99
+ * Checks if auto-injection is enabled for a page
100
+ * @param config - Site configuration
101
+ * @param _page - Page model (reserved for future page-level overrides)
102
+ * @returns true if auto-injection should run
103
+ */
104
+ export function shouldAutoInject(config, _page) {
105
+ // Check global config
106
+ const globalEnabled = config.seo?.autoInject !== false;
107
+ return globalEnabled;
108
+ }