@stati/core 1.16.3 → 1.18.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 (48) hide show
  1. package/dist/core/build.d.ts +41 -12
  2. package/dist/core/build.d.ts.map +1 -1
  3. package/dist/core/build.js +95 -13
  4. package/dist/core/index.d.ts +1 -1
  5. package/dist/core/index.d.ts.map +1 -1
  6. package/dist/core/markdown.d.ts +19 -2
  7. package/dist/core/markdown.d.ts.map +1 -1
  8. package/dist/core/markdown.js +81 -2
  9. package/dist/core/templates.d.ts +11 -2
  10. package/dist/core/templates.d.ts.map +1 -1
  11. package/dist/core/templates.js +28 -11
  12. package/dist/core/utils/index.d.ts +1 -0
  13. package/dist/core/utils/index.d.ts.map +1 -1
  14. package/dist/core/utils/index.js +2 -0
  15. package/dist/core/utils/slugify.utils.d.ts +22 -0
  16. package/dist/core/utils/slugify.utils.d.ts.map +1 -0
  17. package/dist/core/utils/slugify.utils.js +108 -0
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -0
  21. package/dist/metrics/index.d.ts +38 -0
  22. package/dist/metrics/index.d.ts.map +1 -0
  23. package/dist/metrics/index.js +42 -0
  24. package/dist/metrics/noop.d.ts +12 -0
  25. package/dist/metrics/noop.d.ts.map +1 -0
  26. package/dist/metrics/noop.js +88 -0
  27. package/dist/metrics/recorder.d.ts +31 -0
  28. package/dist/metrics/recorder.d.ts.map +1 -0
  29. package/dist/metrics/recorder.js +176 -0
  30. package/dist/metrics/types.d.ts +236 -0
  31. package/dist/metrics/types.d.ts.map +1 -0
  32. package/dist/metrics/types.js +7 -0
  33. package/dist/metrics/utils/index.d.ts +9 -0
  34. package/dist/metrics/utils/index.d.ts.map +1 -0
  35. package/dist/metrics/utils/index.js +9 -0
  36. package/dist/metrics/utils/system.utils.d.ts +44 -0
  37. package/dist/metrics/utils/system.utils.d.ts.map +1 -0
  38. package/dist/metrics/utils/system.utils.js +95 -0
  39. package/dist/metrics/utils/writer.utils.d.ts +64 -0
  40. package/dist/metrics/utils/writer.utils.d.ts.map +1 -0
  41. package/dist/metrics/utils/writer.utils.js +145 -0
  42. package/dist/types/config.d.ts +2 -0
  43. package/dist/types/config.d.ts.map +1 -1
  44. package/dist/types/content.d.ts +23 -4
  45. package/dist/types/content.d.ts.map +1 -1
  46. package/dist/types/index.d.ts +1 -1
  47. package/dist/types/index.d.ts.map +1 -1
  48. package/package.json +1 -1
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Slugify utilities for generating URL-friendly anchor IDs.
3
+ * @module core/utils/slugify
4
+ */
5
+ /**
6
+ * Converts a string to a URL-friendly slug.
7
+ * Used for generating heading anchor IDs.
8
+ *
9
+ * @param text - The text to slugify
10
+ * @returns A URL-friendly slug
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * slugify('Hello World') // 'hello-world'
15
+ * slugify('Getting Started!') // 'getting-started'
16
+ * slugify(' Multiple Spaces ') // 'multiple-spaces'
17
+ * slugify('Héllo Wörld') // 'hello-world'
18
+ * slugify('Cats & Dogs') // 'cats-and-dogs'
19
+ * ```
20
+ */
21
+ export declare function slugify(text: string): string;
22
+ //# sourceMappingURL=slugify.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slugify.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/slugify.utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAyDH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqC5C"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Slugify utilities for generating URL-friendly anchor IDs.
3
+ * @module core/utils/slugify
4
+ */
5
+ /**
6
+ * Common character transliteration map for accented/special characters.
7
+ */
8
+ const TRANSLITERATION_MAP = {
9
+ // Latin extended characters
10
+ à: 'a',
11
+ á: 'a',
12
+ â: 'a',
13
+ ã: 'a',
14
+ ä: 'a',
15
+ å: 'a',
16
+ æ: 'ae',
17
+ ç: 'c',
18
+ č: 'c',
19
+ ć: 'c',
20
+ ð: 'd',
21
+ đ: 'd',
22
+ è: 'e',
23
+ é: 'e',
24
+ ê: 'e',
25
+ ë: 'e',
26
+ ě: 'e',
27
+ ì: 'i',
28
+ í: 'i',
29
+ î: 'i',
30
+ ï: 'i',
31
+ ł: 'l',
32
+ ñ: 'n',
33
+ ń: 'n',
34
+ ò: 'o',
35
+ ó: 'o',
36
+ ô: 'o',
37
+ õ: 'o',
38
+ ö: 'o',
39
+ ø: 'o',
40
+ œ: 'oe',
41
+ ř: 'r',
42
+ š: 's',
43
+ ś: 's',
44
+ ß: 'ss',
45
+ ù: 'u',
46
+ ú: 'u',
47
+ û: 'u',
48
+ ü: 'u',
49
+ ů: 'u',
50
+ ű: 'u',
51
+ ý: 'y',
52
+ ÿ: 'y',
53
+ ž: 'z',
54
+ ź: 'z',
55
+ ż: 'z',
56
+ // Additional common characters
57
+ þ: 'th',
58
+ };
59
+ /**
60
+ * Converts a string to a URL-friendly slug.
61
+ * Used for generating heading anchor IDs.
62
+ *
63
+ * @param text - The text to slugify
64
+ * @returns A URL-friendly slug
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * slugify('Hello World') // 'hello-world'
69
+ * slugify('Getting Started!') // 'getting-started'
70
+ * slugify(' Multiple Spaces ') // 'multiple-spaces'
71
+ * slugify('Héllo Wörld') // 'hello-world'
72
+ * slugify('Cats & Dogs') // 'cats-and-dogs'
73
+ * ```
74
+ */
75
+ export function slugify(text) {
76
+ // Handle null, undefined, or non-string input defensively
77
+ if (text === null || text === undefined) {
78
+ return '';
79
+ }
80
+ // Coerce to string if needed
81
+ const input = String(text);
82
+ return (input
83
+ .toLowerCase()
84
+ .trim()
85
+ // Normalize unicode (NFD decomposes, then we handle diacritics)
86
+ .normalize('NFD')
87
+ // Apply transliteration map for known characters
88
+ // eslint-disable-next-line no-control-regex
89
+ .replace(/[^\u0000-\u007F]/g, (char) => {
90
+ // First check our transliteration map
91
+ if (TRANSLITERATION_MAP[char]) {
92
+ return TRANSLITERATION_MAP[char];
93
+ }
94
+ // Remove combining diacritical marks (accents) that remain after NFD normalization
95
+ if (/[\u0300-\u036f]/.test(char)) {
96
+ return '';
97
+ }
98
+ // Replace other non-ASCII characters (emojis, symbols, CJK, etc.) with space
99
+ // to avoid creating consecutive dashes
100
+ return ' ';
101
+ })
102
+ // Replace & with 'and' (handle case where it wasn't caught by transliteration)
103
+ .replace(/&/g, '-and-')
104
+ // Replace any sequence of whitespace or non-word characters with a single dash
105
+ .replace(/[\s\W-]+/g, '-')
106
+ // Remove leading and trailing dashes
107
+ .replace(/^-+|-+$/g, ''));
108
+ }
package/dist/index.d.ts CHANGED
@@ -23,8 +23,10 @@ export type { StatiConfig, PageModel, FrontMatter, BuildContext, PageContext, Bu
23
23
  export type { SEOMetadata, SEOConfig, SEOContext, SEOValidationResult, SEOTagType, RobotsConfig, OpenGraphConfig, OpenGraphImage, OpenGraphArticle, TwitterCardConfig, AuthorConfig, } from './types/index.js';
24
24
  export type { SitemapConfig, SitemapEntry, SitemapGenerationResult, ChangeFrequency, } from './types/index.js';
25
25
  export type { RSSConfig, RSSFeedConfig, RSSGenerationResult } from './types/index.js';
26
- export type { BuildOptions, DevServerOptions, PreviewServerOptions, InvalidationResult, } from './core/index.js';
26
+ export type { BuildOptions, MetricsOptions, BuildResult, DevServerOptions, PreviewServerOptions, InvalidationResult, } from './core/index.js';
27
27
  export { build, createDevServer, createPreviewServer, invalidate } from './core/index.js';
28
+ export type { BuildMetrics, MetricsMeta, MetricsFlags, MetricsTotals, MetricsPhases, MetricsCounts, MetricsISG, PageTiming, IncrementalMetrics, MetricRecorder, MetricRecorderOptions, PhaseName, CounterName, } from './metrics/index.js';
29
+ export { createMetricRecorder, noopMetricRecorder, writeMetrics, formatMetricsSummary, } from './metrics/index.js';
28
30
  export type { AutoInjectOptions } from './seo/index.js';
29
31
  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
32
  export type { RSSValidationResult } from './rss/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,EACV,YAAY,GACb,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,EAAE,SAAS,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGtF,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,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,GACtB,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"}
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,EACV,YAAY,GACb,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,EAAE,SAAS,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGtF,YAAY,EACV,YAAY,EACZ,cAAc,EACd,WAAW,EACX,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,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,aAAa,EACb,aAAa,EACb,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,SAAS,EACT,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,YAAY,EACZ,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAG5B,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,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,GACtB,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
@@ -20,6 +20,7 @@
20
20
  * ```
21
21
  */
22
22
  export { build, createDevServer, createPreviewServer, invalidate } from './core/index.js';
23
+ export { createMetricRecorder, noopMetricRecorder, writeMetrics, formatMetricsSummary, } from './metrics/index.js';
23
24
  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
25
  export { generateRSSFeed, generateRSSFeeds, validateRSSConfig, validateRSSFeedConfig, } from './rss/index.js';
25
26
  // Re-export config and env utilities
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Stati Build Metrics System
3
+ *
4
+ * Provides performance measurement and reporting for Stati builds.
5
+ * Uses Node.js built-ins only - no external dependencies.
6
+ *
7
+ * @example Basic Usage
8
+ * ```typescript
9
+ * import { createMetricRecorder, writeMetrics, formatMetricsSummary } from '@stati/core';
10
+ *
11
+ * // Create recorder (noop when disabled)
12
+ * const recorder = createMetricRecorder({ enabled: true });
13
+ *
14
+ * // Record phases with spans
15
+ * const endConfig = recorder.startSpan('configLoadMs');
16
+ * await loadConfig();
17
+ * endConfig();
18
+ *
19
+ * // Increment counters
20
+ * recorder.increment('renderedPages');
21
+ *
22
+ * // Finalize and write
23
+ * const metrics = recorder.finalize();
24
+ * await writeMetrics(metrics, { cacheDir: '.stati' });
25
+ *
26
+ * // Format for CLI
27
+ * const summary = formatMetricsSummary(metrics);
28
+ * summary.forEach(line => console.log(line));
29
+ * ```
30
+ *
31
+ * @packageDocumentation
32
+ */
33
+ export type { BuildMetrics, MetricsMeta, MetricsFlags, MetricsTotals, MetricsPhases, MetricsCounts, MetricsISG, PageTiming, IncrementalMetrics, MetricRecorder, MetricRecorderOptions, PhaseName, CounterName, GaugeName, } from './types.js';
34
+ export { createMetricRecorder } from './recorder.js';
35
+ export { noopMetricRecorder } from './noop.js';
36
+ export { writeMetrics, formatMetricsSummary, generateMetricsFilename, DEFAULT_METRICS_DIR, isCI, getGitCommit, getGitBranch, getCpuCount, getPlatform, getArch, getNodeVersion, getMemoryUsage, } from './utils/index.js';
37
+ export type { WriteMetricsOptions, WriteMetricsResult } from './utils/index.js';
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/metrics/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAGH,YAAY,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,aAAa,EACb,aAAa,EACb,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,SAAS,EACT,WAAW,EACX,SAAS,GACV,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAGrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAG/C,OAAO,EAEL,YAAY,EACZ,oBAAoB,EACpB,uBAAuB,EACvB,mBAAmB,EAEnB,IAAI,EACJ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,OAAO,EACP,cAAc,EACd,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Stati Build Metrics System
3
+ *
4
+ * Provides performance measurement and reporting for Stati builds.
5
+ * Uses Node.js built-ins only - no external dependencies.
6
+ *
7
+ * @example Basic Usage
8
+ * ```typescript
9
+ * import { createMetricRecorder, writeMetrics, formatMetricsSummary } from '@stati/core';
10
+ *
11
+ * // Create recorder (noop when disabled)
12
+ * const recorder = createMetricRecorder({ enabled: true });
13
+ *
14
+ * // Record phases with spans
15
+ * const endConfig = recorder.startSpan('configLoadMs');
16
+ * await loadConfig();
17
+ * endConfig();
18
+ *
19
+ * // Increment counters
20
+ * recorder.increment('renderedPages');
21
+ *
22
+ * // Finalize and write
23
+ * const metrics = recorder.finalize();
24
+ * await writeMetrics(metrics, { cacheDir: '.stati' });
25
+ *
26
+ * // Format for CLI
27
+ * const summary = formatMetricsSummary(metrics);
28
+ * summary.forEach(line => console.log(line));
29
+ * ```
30
+ *
31
+ * @packageDocumentation
32
+ */
33
+ // Recorder factory
34
+ export { createMetricRecorder } from './recorder.js';
35
+ // Noop recorder singleton
36
+ export { noopMetricRecorder } from './noop.js';
37
+ // Utilities (file writing, system info)
38
+ export {
39
+ // Writer utilities
40
+ writeMetrics, formatMetricsSummary, generateMetricsFilename, DEFAULT_METRICS_DIR,
41
+ // System utilities
42
+ isCI, getGitCommit, getGitBranch, getCpuCount, getPlatform, getArch, getNodeVersion, getMemoryUsage, } from './utils/index.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Noop MetricRecorder implementation.
3
+ * Used when metrics collection is disabled - has zero overhead.
4
+ * (Null Object Pattern)
5
+ */
6
+ import type { MetricRecorder } from './types.js';
7
+ /**
8
+ * Singleton noop recorder instance.
9
+ * Use this when metrics are disabled for zero-overhead operation.
10
+ */
11
+ export declare const noopMetricRecorder: MetricRecorder;
12
+ //# sourceMappingURL=noop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"noop.d.ts","sourceRoot":"","sources":["../../src/metrics/noop.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,cAAc,EAOf,MAAM,YAAY,CAAC;AAiGpB;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,cAAyC,CAAC"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Noop MetricRecorder implementation.
3
+ * Used when metrics collection is disabled - has zero overhead.
4
+ * (Null Object Pattern)
5
+ */
6
+ /**
7
+ * Empty function that does nothing.
8
+ */
9
+ const noop = () => {
10
+ /* intentionally empty */
11
+ };
12
+ /**
13
+ * Noop implementation of MetricRecorder.
14
+ * All methods are no-ops with minimal overhead.
15
+ */
16
+ class NoopMetricRecorder {
17
+ enabled = false;
18
+ detailed = false;
19
+ startSpan(_name) {
20
+ return noop;
21
+ }
22
+ recordPhase(_name, _durationMs) {
23
+ /* noop */
24
+ }
25
+ addToPhase(_name, _durationMs) {
26
+ /* noop */
27
+ }
28
+ increment(_name, _amount) {
29
+ /* noop */
30
+ }
31
+ setGauge(_name, _value) {
32
+ /* noop */
33
+ }
34
+ recordPageTiming(_url, _durationMs, _cached, _templatesLoaded) {
35
+ /* noop */
36
+ }
37
+ snapshotMemory() {
38
+ /* noop */
39
+ }
40
+ setISGMetrics(_metrics) {
41
+ /* noop */
42
+ }
43
+ setIncrementalMetrics(_metrics) {
44
+ /* noop */
45
+ }
46
+ finalize() {
47
+ // Return minimal valid metrics object
48
+ return {
49
+ schemaVersion: '1',
50
+ meta: {
51
+ timestamp: new Date().toISOString(),
52
+ ci: false,
53
+ nodeVersion: process.version.replace(/^v/, ''),
54
+ platform: process.platform,
55
+ arch: process.arch,
56
+ cpuCount: 1,
57
+ statiVersion: 'unknown',
58
+ command: 'build',
59
+ flags: {},
60
+ },
61
+ totals: {
62
+ durationMs: 0,
63
+ peakRssBytes: 0,
64
+ heapUsedBytes: 0,
65
+ },
66
+ phases: {},
67
+ counts: {
68
+ totalPages: 0,
69
+ renderedPages: 0,
70
+ cachedPages: 0,
71
+ assetsCopied: 0,
72
+ templatesLoaded: 0,
73
+ markdownFilesProcessed: 0,
74
+ },
75
+ isg: {
76
+ enabled: false,
77
+ cacheHitRate: 0,
78
+ manifestEntries: 0,
79
+ invalidatedEntries: 0,
80
+ },
81
+ };
82
+ }
83
+ }
84
+ /**
85
+ * Singleton noop recorder instance.
86
+ * Use this when metrics are disabled for zero-overhead operation.
87
+ */
88
+ export const noopMetricRecorder = new NoopMetricRecorder();
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Active MetricRecorder implementation.
3
+ * Uses performance.now() from node:perf_hooks for high-resolution timing.
4
+ */
5
+ import type { MetricRecorder, MetricRecorderOptions } from './types.js';
6
+ /**
7
+ * Create a MetricRecorder instance.
8
+ * Returns a noop recorder when disabled for zero overhead.
9
+ *
10
+ * @param options - Recorder configuration
11
+ * @returns MetricRecorder instance
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * // Create an enabled recorder
16
+ * const recorder = createMetricRecorder({ enabled: true });
17
+ *
18
+ * // Start a span
19
+ * const endSpan = recorder.startSpan('configLoadMs');
20
+ * await loadConfig();
21
+ * endSpan(); // Records duration
22
+ *
23
+ * // Increment counters
24
+ * recorder.increment('renderedPages');
25
+ *
26
+ * // Finalize and get metrics
27
+ * const metrics = recorder.finalize();
28
+ * ```
29
+ */
30
+ export declare function createMetricRecorder(options?: MetricRecorderOptions): MetricRecorder;
31
+ //# sourceMappingURL=recorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/metrics/recorder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EAQtB,MAAM,YAAY,CAAC;AAiNpB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,qBAA0B,GAAG,cAAc,CAaxF"}
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Active MetricRecorder implementation.
3
+ * Uses performance.now() from node:perf_hooks for high-resolution timing.
4
+ */
5
+ import { performance } from 'node:perf_hooks';
6
+ import { isCI, getGitCommit, getGitBranch, getCpuCount, getPlatform, getArch, getNodeVersion, getMemoryUsage, } from './utils/index.js';
7
+ import { noopMetricRecorder } from './noop.js';
8
+ /**
9
+ * Active metric recorder that collects build performance data.
10
+ */
11
+ class ActiveMetricRecorder {
12
+ enabled = true;
13
+ detailed;
14
+ startTime;
15
+ command;
16
+ flags;
17
+ statiVersion;
18
+ phases = {};
19
+ counts = {
20
+ totalPages: 0,
21
+ renderedPages: 0,
22
+ cachedPages: 0,
23
+ assetsCopied: 0,
24
+ templatesLoaded: 0,
25
+ markdownFilesProcessed: 0,
26
+ };
27
+ isgMetrics = {
28
+ enabled: false,
29
+ cacheHitRate: 0,
30
+ manifestEntries: 0,
31
+ invalidatedEntries: 0,
32
+ };
33
+ incrementalMetrics;
34
+ pageTimings = [];
35
+ gauges = new Map();
36
+ constructor(options = {}) {
37
+ this.startTime = performance.now();
38
+ this.detailed = options.detailed ?? false;
39
+ this.command = options.command ?? 'build';
40
+ this.flags = options.flags ?? {};
41
+ this.statiVersion = options.statiVersion ?? 'unknown';
42
+ // Take initial memory snapshot
43
+ this.snapshotMemory();
44
+ }
45
+ startSpan(name) {
46
+ const spanStart = performance.now();
47
+ return () => {
48
+ const duration = performance.now() - spanStart;
49
+ this.recordPhase(name, duration);
50
+ };
51
+ }
52
+ recordPhase(name, durationMs) {
53
+ // Record phase timing using the phase name directly (replaces)
54
+ this.phases[name] = durationMs;
55
+ }
56
+ addToPhase(name, durationMs) {
57
+ // Add to existing phase timing (accumulates)
58
+ const current = this.phases[name] ?? 0;
59
+ this.phases[name] = current + durationMs;
60
+ }
61
+ increment(name, amount = 1) {
62
+ const current = this.counts[name] ?? 0;
63
+ this.counts[name] = current + amount;
64
+ }
65
+ setGauge(name, value) {
66
+ // Gauges track the maximum value observed for a given name
67
+ const current = this.gauges.get(name) ?? 0;
68
+ this.gauges.set(name, Math.max(current, value));
69
+ }
70
+ recordPageTiming(url, durationMs, cached, templatesLoaded) {
71
+ if (this.detailed) {
72
+ const timing = { url, durationMs, cached };
73
+ if (templatesLoaded !== undefined) {
74
+ this.pageTimings.push({ ...timing, templatesLoaded });
75
+ }
76
+ else {
77
+ this.pageTimings.push(timing);
78
+ }
79
+ }
80
+ }
81
+ snapshotMemory() {
82
+ const usage = getMemoryUsage();
83
+ this.setGauge('peakRss', usage.rss);
84
+ }
85
+ setISGMetrics(metrics) {
86
+ this.isgMetrics = { ...this.isgMetrics, ...metrics };
87
+ }
88
+ setIncrementalMetrics(metrics) {
89
+ this.incrementalMetrics = metrics;
90
+ }
91
+ finalize() {
92
+ // Take final memory snapshot
93
+ this.snapshotMemory();
94
+ const finalMemory = getMemoryUsage();
95
+ const totalDuration = performance.now() - this.startTime;
96
+ // Build the meta object, excluding undefined git values
97
+ const gitCommit = getGitCommit();
98
+ const gitBranch = getGitBranch();
99
+ const meta = {
100
+ timestamp: new Date().toISOString(),
101
+ ci: isCI(),
102
+ nodeVersion: getNodeVersion(),
103
+ platform: getPlatform(),
104
+ arch: getArch(),
105
+ cpuCount: getCpuCount(),
106
+ statiVersion: this.statiVersion,
107
+ command: this.command,
108
+ flags: this.flags,
109
+ ...(gitCommit !== undefined && { gitCommit }),
110
+ ...(gitBranch !== undefined && { gitBranch }),
111
+ };
112
+ // Build the base metrics object
113
+ const baseMetrics = {
114
+ schemaVersion: '1',
115
+ meta,
116
+ totals: {
117
+ durationMs: totalDuration,
118
+ peakRssBytes: this.peakRss,
119
+ heapUsedBytes: finalMemory.heapUsed,
120
+ },
121
+ phases: this.phases,
122
+ counts: this.counts,
123
+ isg: this.isgMetrics,
124
+ };
125
+ // Add optional fields conditionally
126
+ const optionalFields = {};
127
+ if (this.detailed && this.pageTimings.length > 0) {
128
+ optionalFields.pageTimings = this.pageTimings;
129
+ }
130
+ if (this.incrementalMetrics) {
131
+ optionalFields.incremental = this.incrementalMetrics;
132
+ }
133
+ return { ...baseMetrics, ...optionalFields };
134
+ }
135
+ /**
136
+ * Get the peak RSS value from gauges.
137
+ */
138
+ get peakRss() {
139
+ return this.gauges.get('peakRss') ?? 0;
140
+ }
141
+ }
142
+ /**
143
+ * Create a MetricRecorder instance.
144
+ * Returns a noop recorder when disabled for zero overhead.
145
+ *
146
+ * @param options - Recorder configuration
147
+ * @returns MetricRecorder instance
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * // Create an enabled recorder
152
+ * const recorder = createMetricRecorder({ enabled: true });
153
+ *
154
+ * // Start a span
155
+ * const endSpan = recorder.startSpan('configLoadMs');
156
+ * await loadConfig();
157
+ * endSpan(); // Records duration
158
+ *
159
+ * // Increment counters
160
+ * recorder.increment('renderedPages');
161
+ *
162
+ * // Finalize and get metrics
163
+ * const metrics = recorder.finalize();
164
+ * ```
165
+ */
166
+ export function createMetricRecorder(options = {}) {
167
+ if (options.enabled === false) {
168
+ return noopMetricRecorder;
169
+ }
170
+ // Check environment variable for CI metrics
171
+ const envEnabled = process.env.STATI_METRICS === '1' || process.env.STATI_METRICS === 'true';
172
+ if (!options.enabled && !envEnabled) {
173
+ return noopMetricRecorder;
174
+ }
175
+ return new ActiveMetricRecorder(options);
176
+ }